Skip to content

fix(source-control): the worktree-create gate's failure output reports a constant exit status, discards the helper's exit taxonomy, and names no remedy #2209

Description

@kyle-sexton

With the non-git deadlock resolved as an upstream harness defect (see the upstream-draft tracker), the failure message is the only thing this plugin controls that a stuck user will ever see. Every part of it is currently wrong or useless: the exit status it prints is a constant, the helper's four-way exit taxonomy is discarded before it can be reported, the first stderr line — the one guaranteed to reach the transcript — carries a diagnostic rather than an action, and a distinct failure mode is reported under the wrong cause. These four are one file, one sitting.

Rows

  • I9 SC-F2plugins/source-control/hooks/worktree-create-gate.sh:144-148status=$? inside if ! path="$(…)" is always 0, so the message reports a constant, not the helper's real exit code.
  • I9 SC-F3plugins/source-control/scripts/worktree-create.sh:29-33plugins/source-control/hooks/worktree-create-gate.sh:144-148 — the helper's documented 0/2/3/4 exit taxonomy collapses onto a single gate exit 1, so "not a repository", "no worktree_root configured" and "illegal branch name" are indistinguishable to the user.
  • I9 SC-F4plugins/source-control/scripts/worktree-create.sh:259 and plugins/source-control/hooks/worktree-create-gate.sh:146 — neither stderr string names a remedy, and the first line (the one the transcript surfaces) is the least actionable of the two.
  • I9 SC-F10plugins/source-control/hooks/worktree-create-gate.sh:67 and :92hook::buffer_stdin's failure status is ignored, so an empty or unparseable payload surfaces as "the WorktreeCreate payload carried no .name" — the wrong cause.

Evidence

SC-F2 / SC-F3 — the status is a constant, the taxonomy is discarded

plugins/source-control/hooks/worktree-create-gate.sh:144-148:

if ! path="$(bash "$helper" "${args[@]}")"; then
  status=$?
  printf 'worktree-create-gate: %s exited %s; worktree not created\n' "$helper" "$status" >&2
  exit 1
fi

$? in the body of if ! cmd is the status of the negated compound, which is 0 whenever cmd failed. Proven with a function returning 7. The taxonomy being thrown away is documented at plugins/source-control/scripts/worktree-create.sh:29-33:

#   0  success — worktree created; path on stdout
#   2  usage error — unknown/missing flag, or a --name git rejects as a branch
#   3  refuse — no usable external root (guidance on stderr); nothing created
#   4  environment error — not a git repo, or `git worktree add` failed

The exit 1 direction is correct and must be preserved — https://code.claude.com/docs/en/hooks, fetched 2026-08-11: "The exception is WorktreeCreate, where any non-zero exit code aborts worktree creation." The defect is confined to the message text. Note the honest constraint from the audit: even perfect propagation would not let the hook say "not applicable" (that is an upstream gap), so the value here is diagnostic and human-facing, not a behavior change.

This bug has already cost real time: the constant 0 is what produced the "a hook exited 0 while failing" theory that a whole verification pass had to unwind.

SC-F4 — no remedy in either line

The two producing sites, plugins/source-control/scripts/worktree-create.sh:259:

printf '%s: --repo-dir is not inside a git repository: %s\n' "$PROG" "$repo_dir" >&2

and hooks/worktree-create-gate.sh:146 (quoted above). Neither string contains an action a user can take.

Wording constraint if implemented: the remedy line should name worktree.bgIsolation: "none" — a genuine harness-side stand-down, confirmed verbatim at https://code.claude.com/docs/en/agent-view, fetched 2026-08-11: "To turn off worktree isolation for a repository where git worktrees are impractical, set worktree.bgIsolation to \"none\"." It must not advertise worktree_create_gate_enabled as relief until that option's behavior is measured (see the gate scope/opt-out issue, cross-linked below).

Partially-confirmed doc leg: the "stderr from a hook that exits 0 goes to the debug log only, never the transcript" half was re-confirmed verbatim on 2026-08-11; the "transcript surfaces the first line of stderr" half is auditor-only (2026-08-10) and was not independently re-fetched.

SC-F10 — the wrong cause is printed

plugins/source-control/hooks/worktree-create-gate.sh:67:

payload="$(hook::buffer_stdin)"

— no status check. plugins/source-control/hooks/hook-utils.sh:596 (inside hook::buffer_stdin, which begins at :522):

[[ -n "$input" ]] || return 1

The misattributing message is at hooks/worktree-create-gate.sh:92 → "the WorktreeCreate payload carried no .name; refusing rather than guessing a worktree name". The direction of failure is correct (fail-closed); this is message accuracy only. The jq-absent fail-open path is handled correctly (jq_rc == 127 passes the raw buffer through to the sed fallback at gate :72-86) and should not be disturbed.

Adjacent: #2146 ("every hook calling hook::require_jq fails OPEN when jq is absent, while the same guard fails closed on length") touches the same utility file — different function, not a duplicate, but read it before editing hook-utils.sh.

Provenance

Severity: MED (cluster highest; SC-F10 is LOW) · Provenance: AUDITOR_VERIFIED
Origin: handoff-inbox item 20260811-021645-plugin-audit-four-components-and-guard-deadlock-ownership
Ledger: .work/handoff-inbox-batch-4/ledgers/I9-021645-four-components.md § "Lane D — source-control / worktree-create-gate"
Verified against repo HEAD 685dd381.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions