Skip to content

T-WOL2-task-work-preflight-probes-file-mutation-tools

Status: closed/done · Impact: medium · Complexity: small

Spawned from T-M5RS-port-check-entities-to-ts in git@github.com:sksizer/dev.git. The check_entities port run hit a file-mutation permission denial that task-work’s preflight did not catch.

task-work’s Step 3b preflight_permissions.ts only probes Bash tool-family signals, not file-mutation tools (Write/Edit). On the check_entities port run, Write/Edit were denied in the worktree and the implementer had to fall back to Bash heredoc for file creation. Extend the preflight to confirm Write/Edit permission coverage so file-mutation denials surface before implementation rather than mid-flight.

The Step-3b probe scans the task body for Bash(<verb>:*) tool-family signals and reports missing Bash grants. It has no concept of file-mutation tools: it never checks whether Write / Edit are permitted for the worktree path the implementer will create in Step 4. When those tools are denied (or not allowed) for .sdlc/worktrees/..., the gap goes undetected until the implementing sub-agent hits the denial mid-flight and falls back to Bash heredoc — exactly the friction the probe exists to prevent.

LocationRole today
plugin/skills/task-work/preflight_permissions.ts#mainThe probe entry point: scans the task body, resolves settings, reports Bash gaps on stdout, exits 0/1/2. Knows nothing about Write/Edit.
plugin/skills/task-work/preflight_permissions.ts#collectPermissionsBuilds the resolved allow/deny sets from the three settings files. Today it only feeds the Bash-verb matcher; the same resolved sets can answer Write/Edit coverage.
plugin/skills/task-work/preflight_permissions.ts#ResolvedPermissionsHolds the allow/deny sets and the allowsVerb Bash matcher. Needs a sibling check for tool-rule entries like Write(...) / Edit(...).
plugin/skills/task-work/SKILL.md#3b.Step 3b’s prose: describes the Bash-signal probe and the AskUserQuestion remediation on exit 1. Must document the new Write/Edit gap shape.
plugin/skills/task-work/tests/preflight_permissions.test.tsThe bun:test suite (spawnSync against the probe). The new Write/Edit coverage needs cases here.
plugin/skills/task-work/invariants.yaml#required_phrasesPins the literal preflight_permissions.ts reference to Step 3b — the prose edit must keep that pin satisfied.

The Step-3b probe additionally checks whether the Write and Edit tools are granted for the worktree path the task will use (.sdlc/worktrees/<basename>/). When either is missing (absent from allow, or matched by deny), the probe reports it on stdout in a shape parallel to the Bash gaps (e.g. Write: missing Write(...)), exits 1, and Step 3b surfaces it through the same AskUserQuestion remediation. File-mutation denials surface before the worktree is created rather than mid-implementation.

  1. Add a tool-rule matcher to preflight_permissions.ts alongside allowsVerb: a method on ResolvedPermissions (e.g. allowsTool(tool, targetPath)) that returns true iff a <tool>(...) allow rule covers targetPath and no <tool>(...) deny rule matches it. Honour the bare-tool form ("Edit" / "Write" with no parens = all paths) and the path-scoped form (Edit(//abs/path/**) / Edit(~/rel/**)), resolving the same ~/path semantics Claude Code uses. Treat defaultMode: acceptEdits/bypassPermissions (read from the same settings files) as blanket allow.
  2. In main, after the Bash-signal gap collection, always probe Write and Edit against the would-be worktree path derived from the task file’s repo root: <repo-root>/.sdlc/worktrees/<task-basename>/. Append any missing tool to the gap list in the shape <tool>: missing <tool>(<worktree-glob>), so the existing exit-1 / one-line-per-gap contract carries the new gaps unchanged.
  3. Update plugin/skills/task-work/SKILL.md Step 3b prose to name the Write/Edit probe and its remediation, keeping the literal preflight_permissions.ts reference (invariant pin) intact.
  4. Extend plugin/skills/task-work/tests/preflight_permissions.test.ts: Write/Edit denied → exit 1 with the new line; granted (bare or path-scoped to the worktree glob) → no new gap; defaultMode: acceptEdits → no gap; deny-beats-allow for tools mirrors the existing Bash case.
  5. Refresh the per-skill doc docs/skills/task-work.md if its Step-3b description enumerates what the probe checks.
LocationKindChange
plugin/skills/task-work/preflight_permissions.tsmodifyAdd allowsTool tool-rule matcher + Write/Edit worktree-path probe in main; honour defaultMode
plugin/skills/task-work/SKILL.mdmodifyStep 3b prose documents the Write/Edit gap shape and remediation
plugin/skills/task-work/tests/preflight_permissions.test.tsmodifyCases for Write/Edit denied / granted / defaultMode / deny-beats-allow
docs/skills/task-work.mdmodifyStep-3b doc reflects the new file-mutation probe (if it enumerates the checks)
  • AC-1: Run against a task whose resolved settings grant Bash but not Write/Edit for the worktree path, preflight_permissions.ts exits 1 and stdout contains a line naming the missing file-mutation tool (e.g. Write: missing Write(...) and/or Edit: missing Edit(...)).
  • AC-2: Run against settings that grant Edit/Write (bare, or path-scoped to .sdlc/worktrees/**) for the worktree path, the probe reports no file-mutation gap; with defaultMode: acceptEdits it also reports no gap.
  • AC-3: A deny entry for Edit(...)/Write(...) matching the worktree path beats a corresponding allow — the probe reports the gap (parallels the existing deny beats allow Bash test).
  • AC-4: Running bun test against plugin/skills/task-work/tests/preflight_permissions.test.ts passes, including new cases for AC-1 / AC-2 / AC-3; the pre-existing Bash-signal tests still pass unchanged.
  • AC-5: plugin/skills/task-work/SKILL.md Step 3b prose names the Write/Edit probe; bun run plugin/scripts/lint_skill_prose.ts plugin/skills/task-work/SKILL.md still finds the preflight_permissions.ts invariant pin satisfied (the prose edit does not break the existing pin).
  • Probing every tool family Claude Code exposes (NotebookEdit, MultiEdit, WebFetch, MCP tools) — only Write and Edit, the two the check_entities run hit. MultiEdit may be added if trivial, but is not required.
  • Auto-granting the missing permission — the probe reports; remediation stays the existing Step-3b AskUserQuestion flow (grant via /config, proceed anyway, or abort).
  • Making the probe block when a gap is found — Step 3b already offers “proceed anyway”; the probe only surfaces, it does not hard-fail.
  • Changing how the worktree path is chosen in Step 4 — the probe derives the same path Step 4 will create; it does not alter the worktree layout.
  • none

Spawned by /sdlc:spawn-task-pr on 2026-06-02 UTC from T-M5RS-port-check-entities-to-ts in git@github.com:sksizer/dev.git.

Captured by /sdlc:task-work on 2026-06-04. PR: pending.

  • AC-1: auto — bun test plugin/skills/task-work/tests/preflight_permissions.test.ts (“AC-1: Write/Edit denied for the worktree path surfaces (exit 1)”): probe exits 1 with Write: missing Write(...) / Edit: missing Edit(...) naming the worktree glob.
  • AC-2: auto — same suite covers bare grants, path-scoped .sdlc/worktrees/** grants, and defaultMode: acceptEdits; each reports no file-mutation gap (exit 0).
  • AC-3: auto — “AC-3: a deny on the worktree path beats an allow” asserts deny-beats-allow parallels the existing Bash case.
  • AC-4: auto — bun test against the suite: 18 pass / 0 fail; the pre-existing Bash-signal tests still pass unchanged.
  • AC-5: auto — SKILL.md Step 3b prose names the Write/Edit probe; bun run plugin/scripts/lint_skill_prose.ts plugin/skills/task-work/SKILL.md reports no violations (the preflight_permissions.ts invariant pin stays satisfied).
  • The implementation was already present (uncommitted) in the worktree from the paused dispatch; resume picked it up cleanly. allowsTool + worktreePathFor were complete and correct against the ACs.
  • tsc --noEmit and the targeted bun test gave a fast, unambiguous green signal independent of the corpus-wide audit noise.
  • The baseline-gated quality gate reported 8 new-drift lines that were all benign - OK audit-status lines from audit_entities.ts, flagged only because the baseline SHA (origin/main) predated the local-main commits the worktree branched from and the main checkout carried uncommitted task-file WIP — the per-entity OK lines should be excluded from the baseline diff (only non-OK audit lines are real drift signal), or the baseline should be captured against local main’s tip rather than origin/main when they diverge. → T-TWZD-normalize-baseline-diff-nondeterministic-output

← Back to Tasks