fix(kanban): scratch tasks must not inherit board.default_workdir (#28818)
Board defaults represent persistent project checkouts. Scratch workspaces are auto-deleted on completion and must stay under the per-board scratch root that resolve_workspace() creates. Inheriting default_workdir for a scratch task pointed the cleanup path at the user's source tree — the data-loss vector documented in #28818. The containment guard in _cleanup_workspace (just added) is the safety rail. This commit prevents the bad state from being created in the first place: only persistent kinds (dir/worktree) inherit board defaults. Tests updated to cover the new semantics: scratch with default_workdir set keeps workspace_path=None; dir/worktree still inherits the board default. Salvaged from PR #31315 by @leeseoki0 — prevention layer on top of the #28819 containment fix by @briandevans. Co-authored-by: teknium1 <127238744+teknium1@users.noreply.github.com>
This commit is contained in:
parent
23115b5c0f
commit
ce529d6072
@ -1651,8 +1651,15 @@ def create_task(
|
|||||||
now = int(time.time())
|
now = int(time.time())
|
||||||
|
|
||||||
# Resolve workspace_path from board-level default_workdir when the
|
# Resolve workspace_path from board-level default_workdir when the
|
||||||
# caller did not specify one explicitly.
|
# caller did not specify one explicitly. Board defaults represent
|
||||||
if workspace_path is None:
|
# persistent project checkouts, so only persistent workspace kinds may
|
||||||
|
# inherit them. Scratch workspaces are auto-deleted on completion and
|
||||||
|
# must stay under the per-board scratch root created by
|
||||||
|
# ``resolve_workspace``; inheriting ``default_workdir`` for a scratch
|
||||||
|
# task would point cleanup at the user's source tree (#28818). The
|
||||||
|
# containment guard in ``_cleanup_workspace`` is the safety rail, but
|
||||||
|
# we also stop the bad state from being created in the first place.
|
||||||
|
if workspace_path is None and workspace_kind in {"dir", "worktree"}:
|
||||||
board_slug = board if board else get_current_board()
|
board_slug = board if board else get_current_board()
|
||||||
board_meta = read_board_metadata(board_slug)
|
board_meta = read_board_metadata(board_slug)
|
||||||
board_default = board_meta.get("default_workdir")
|
board_default = board_meta.get("default_workdir")
|
||||||
|
|||||||
@ -2596,13 +2596,32 @@ def test_task_dict_survives_corrupt_created_at(tmp_path, monkeypatch):
|
|||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
def test_create_task_without_workspace_inherits_board_default_workdir(kanban_home, monkeypatch):
|
def test_create_task_scratch_without_workspace_ignores_board_default_workdir(kanban_home, monkeypatch):
|
||||||
"""Board with default_workdir → create_task without workspace_path → inherits default."""
|
"""Scratch tasks must NOT inherit board.default_workdir — would point auto-cleanup
|
||||||
|
at the user's source tree on completion (#28818)."""
|
||||||
default_wd = "/home/user/project"
|
default_wd = "/home/user/project"
|
||||||
kb.create_board("work-proj", default_workdir=default_wd)
|
kb.create_board("work-proj", default_workdir=default_wd)
|
||||||
|
|
||||||
with kb.connect(board="work-proj") as conn:
|
with kb.connect(board="work-proj") as conn:
|
||||||
tid = kb.create_task(conn, title="inherited", board="work-proj")
|
tid = kb.create_task(conn, title="scratch-task", board="work-proj")
|
||||||
|
t = kb.get_task(conn, tid)
|
||||||
|
assert t is not None
|
||||||
|
assert t.workspace_kind == "scratch"
|
||||||
|
assert t.workspace_path is None
|
||||||
|
|
||||||
|
|
||||||
|
def test_create_task_dir_without_workspace_inherits_board_default_workdir(kanban_home, monkeypatch):
|
||||||
|
"""Board default_workdir is for persistent dir/worktree workspaces, not scratch."""
|
||||||
|
default_wd = "/home/user/project"
|
||||||
|
kb.create_board("work-proj-dir", default_workdir=default_wd)
|
||||||
|
|
||||||
|
with kb.connect(board="work-proj-dir") as conn:
|
||||||
|
tid = kb.create_task(
|
||||||
|
conn,
|
||||||
|
title="inherited",
|
||||||
|
workspace_kind="dir",
|
||||||
|
board="work-proj-dir",
|
||||||
|
)
|
||||||
t = kb.get_task(conn, tid)
|
t = kb.get_task(conn, tid)
|
||||||
assert t is not None
|
assert t is not None
|
||||||
assert t.workspace_path == default_wd
|
assert t.workspace_path == default_wd
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user