T-NUML-task-work-preflight-permissions-probe-extension-for-skill-internal-scripts
Status: closed/superseded · Impact: low · Complexity: medium
The pre-flight permissions probe shipped via [T-Z8VC-task-work-preflight-permissions-probe](/planning/tasks/task-work-preflight-permissions-probe/)
checks package-manager grants, body-text tool-family signals, and Write/Edit coverage of the
would-be worktree. It does NOT check the plugin’s own shell-out surface: the unified sdlc CLI
launcher (${CLAUDE_PLUGIN_ROOT}cli/sdlc, which nearly every skill calls) and the handful of
co-located skill scripts. When the sandbox denies one of those, the failure surfaces mid-skill —
not at pre-flight — as an ERROR verdict from a sub-agent that couldn’t run the shell-out. Close
the gap so the plugin’s shell-out paths are checked alongside the tool-family signals.
apps/sdlc/skills/task-work/preflight_permissions.ts (task-work Step 3b) resolves the project’s
package managers into hard gaps, demotes body-text-only package-manager mentions to warnings,
keeps body-text hard gaps for node/npx/pytest, and probes Write/Edit against the
worktree path Step 4 will create. It has no knowledge of the plugin’s own shell-outs: a dispatch
can pass pre-flight and still die at execution time when the sandbox lacks a grant covering the
skill’s shell-out, returning ERROR reason="permission ... denied by the harness".
The current shell-out surface is small — nearly everything goes through one entry point:
| Location | Role today |
|---|---|
apps/sdlc/skills/task-work/preflight_permissions.ts | The probe Step 3b shells out to; covers package managers, non-PM body signals, and Write/Edit — not the plugin surface below |
apps/sdlc/cli/sdlc | Launcher shim for the unified CLI; the dominant shell-out (${CLAUDE_PLUGIN_ROOT}cli/sdlc <noun> <verb>, ~125 call sites across skill prose); needs a Bash(<plugin-root>/cli/sdlc:*) grant or covering glob |
apps/sdlc/skills/task-work/start_task.ts | Example of the co-located .ts scripts skills run via bun run; covered only when Bash(bun:*) is granted |
apps/sdlc/skills/pr-check/post_self_comment.sh | Shell wrapper invoked by path; needs its own grant or covering glob |
apps/sdlc/skills/orchestrate/watch_loop.sh | Shell wrapper invoked by path; same gap |
Proposed
Section titled “Proposed”Extend the probe with a plugin-surface check: verify the resolved sandbox permissions cover each
entry of the plugin’s own shell-out surface (directly or via a covering glob) and report gaps in
the same exit-1, one-line-per-gap shape as the existing checks. The surface is a short static
list — the cli/sdlc launcher, the bun verb for co-located .ts scripts, and the two .sh
wrappers — so encode it in the probe directly. Per-skill permission manifests or dynamic
SKILL.md scanning would be machinery without a payload: the single CLI entry point is where the
surface lives, and it is stable.
/sdlc:setup’s onboarding should also name the recommended grants, so they’re in place at
session start rather than added after a denial. This matters because the harness does not reload
settings.local.json mid-session: a grant added in response to a denial doesn’t reach sub-agents
dispatched later in the same session.
Approach
Section titled “Approach”- Encode the surface: add the plugin shell-out list (launcher path,
bunverb,.shwrapper paths) topreflight_permissions.tsas a static table resolved againstCLAUDE_PLUGIN_ROOT. - Extend the probe: for each surface entry, verify the resolved sandbox grants cover it (reusing the existing verb/path matching); report gaps with the existing exit-1-stdout-lines shape.
- Document the grants in setup: extend
apps/sdlc/skills/setup/SKILL.mdso first-run onboarding names the recommended grant set; re-running setup is a no-op. - Test fixtures: probe against a settings fixture lacking the CLI grant — expect exit 1 and a line naming the uncovered path; with the grants present — expect exit 0.
Files to touch
Section titled “Files to touch”| Location | Kind | Change |
|---|---|---|
apps/sdlc/skills/task-work/preflight_permissions.ts | modify | add the plugin shell-out surface check; report gaps with the existing exit-1 format |
apps/sdlc/skills/setup/SKILL.md | modify | name the recommended plugin-surface grants in first-run onboarding; idempotent |
apps/sdlc/skills/task-work/tests/preflight_permissions.test.ts | modify | fixtures exercising the plugin-surface check with and without the grants present |
Acceptance criteria
Section titled “Acceptance criteria”- AC-1: Running the probe against a sandbox with no plugin-surface grants exits 1 with at least one stdout line naming an uncovered plugin shell-out path.
- AC-2: Running the probe against a sandbox granting the surface (e.g.
Bash(<plugin-root>/cli/sdlc:*)plusBash(bun:*)) exits 0 with no plugin-surface gaps. - AC-3:
/sdlc:setupfirst-run onboarding names the recommended grants; a second run is a no-op. - AC-4: A dispatch whose skill shells out to
${CLAUDE_PLUGIN_ROOT}cli/sdlcagainst a sandbox lacking that grant is caught at pre-flight by the extended probe, not at execution time inside the sub-agent. - AC-5: The project quality checks (
bunx tsc --noEmit,bun test apps/sdlc) pass.
Out of scope
Section titled “Out of scope”- Per-skill permission manifests and dynamic
SKILL.mdscanning — the surface is a handful of stable paths behind one CLI entry point; enumeration machinery isn’t warranted (see Proposed). - Auto-granting permissions — the probe REPORTS gaps; it doesn’t write to sandbox config.
The only install path is the documented
/sdlc:setuponboarding guidance. - Cross-plugin permission concerns — this task is about the sdlc plugin’s own shell-outs. Other plugins handle their own permission story.
Dependencies
Section titled “Dependencies”- none. Builds on the existing pre-flight probe
(
[T-Z8VC-task-work-preflight-permissions-probe](/planning/tasks/task-work-preflight-permissions-probe/)).
Discovery context
Section titled “Discovery context”- Surfaced during a
/sdlc:orchestratesession on 2026-05-24: a dispatched/sdlc:pr-checksub-agent immediately returnedERROR reason="permission ... denied by the harness"because the sandbox lacked a grant covering the skill’s shell-out. The orchestrator fell back to directghqueries, losing the per-PR-cursor advance the skill provides. The probe should have caught the gap at pre-flight instead of spending a dispatch on it. - Same session, a second failure mode that still holds: after the user granted the missing
permissions mid-session, a re-dispatched sub-agent still saw the stale pre-grant settings — the
harness doesn’t reload
settings.local.jsonmid-session. Grants must be present at session start, which is why the recommended grants belong in/sdlc:setup’s onboarding (Approach step 3). - Companion to
[T-NP7H-task-work-sub-agent-verdict-contract-escape-recurrence](/planning/tasks/task-work-sub-agent-verdict-contract-escape-recurrence/)— both surfaced in the same orchestrate session.