fix(session-flow): specify continue-in-background's dirty-tree gate for a non-repo consuming directory - #1728
Conversation
…or a non-repo consuming directory The gate opened by running `git status --porcelain -uall`, which in a directory that is not a git repository fails with `fatal: not a git repository` and leaves the skill with no specified behavior. Establish repository status first with `git rev-parse --is-inside-work-tree` and specify all three outcomes: inside a work tree, inspect as before; not a git repository, launch (no uncommitted work to protect, and outside a repository background sessions write to the working directory directly rather than moving into an isolated worktree); any other failure, treat the tree as UNKNOWN and do not launch. That third branch is what keeps the added case from turning a gate that protects uncommitted work into one that fails open. The post-launch checklist and gotchas index carry the same branches. Two eval cases cover the non-repo launch and the non-"not a git repository" git failure that must not launch. Closes #929 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…reworked gate Enumerating outcomes of `git rev-parse --is-inside-work-tree` as a three-way split left a fourth unrouted: the command exits 0 and prints `false` inside a bare repository or a `.git` directory. Invert the structure — specify the two results that may leave the default (`true` inspects, positively-identified non-repo launches) and route everything else to the conservative unknown branch. The gate is now exhaustive by construction rather than by enumeration. Scope the context block's "treat any failure as an unknown value and carry on" to itself. It colors the save-point and is not the gate, which reads a git failure the opposite way; nothing should carry that shrug, or that block's non-`-uall` `git status` output, into delivery step 1. Add an eval for the zero-exit `false` case. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Claude encountered an error after 1s —— View job I'll analyze this and get back to you. |
|
Claude encountered an error after 1s —— View job I'll analyze this and get back to you. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 748ab3cbc8
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
Claude finished @kyle-sexton's task in 2m 2s —— View job PR Review — fix(session-flow): continue-in-background non-repo gate
OverviewThe gate redesign is correct and the approach is principled. Replacing enumeration with an exhaustive-by-construction structure (two specified results + deliberate wide default) closes the original bug and the bare-repo edge case that the independent review found. The "never route by exit status alone" rule is explicit and correct. The context-block scoping, checklist update, and gotchas index are all consistent with the new gate semantics. One finding worth addressing before merge, and one minor observation: Finding 1 — WorktreeCreate hook not checked before the non-repo launch (Codex P2 finding)File: The non-repo branch launches on the basis that "outside a repository (and absent a If a consumer has a The PR description lists a finding about Options in order of preference:
Observation — Eval #5 expected_output omits the hook qualifierFile: The Everything else looks good
|
|
Claude finished @kyle-sexton's task in 1m 57s —— View job Security Review — PR #1728
No security issues to report. The four changed files are a version bump ( AnalysisGate logic — no failure-open path introduced. The Codex P2 finding — not a security issue in this context.
Command injection surface — unchanged. Privilege / authorization model — unchanged. Summary: Nothing in this diff constitutes a new security finding. The Codex P2 item is a correctness observation about hook behavior documentation — worth its own issue if the team wants to tighten the wording — but it does not represent a vulnerability introduced here. |
…e non-repo launch branch
The dirty-tree gate's non-repo branch launched on `git rev-parse` alone.
The `WorktreeCreate` hook appeared only as a parenthetical premise
("and absent a `WorktreeCreate` hook"), never as something the skill had
to establish, so a non-Git consumer with that hook configured took the
launch path unchecked.
That hook is the documented isolation path for non-Git source control
(`plugins/playbooks/skills/boris/reference/worktrees.md`) and "replaces
the default worktree creation entirely"
(`docs/conventions/topic-docs/README.md`). With one configured, the
launched session moves into a hook-created workspace the consuming
checkout's local changes never reach — precisely the loss the dirty-tree
gate exists to prevent, so the gate failed open on that case.
Hook absence is now part of the branch's condition rather than an
assumed premise. Not-a-git-repository AND no hook configured → launch;
a configured hook, or an absence that cannot be established → the
existing wide unknown default, which does not launch. No hook-detection
command is prescribed: the branch fails closed when absence cannot be
established, so it needs none.
Carried to the surfaces that restate the same rule: the post-launch
enforcement checklist, the gotchas index, and the unreleased 0.17.17
CHANGELOG entry. Eval case 5 stated the non-repo prompt without the hook
condition and would have contradicted the fixed gate; it now states it,
and case 8 covers a Mercurial checkout with the hook configured.
Refs #929
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014xc3oV3XyPqTHKa2FSXssF
|
Claude finished @kyle-sexton's task in 2m 3s —— View job PR Review — fix(session-flow): continue-in-background non-repo gate
What's new in this updateThe latest commit ( Finding 1 (WorktreeCreate hook not checked) — Fixed. The non-repo branch in SKILL.md (lines 79–86) now makes hook absence a required condition, not a parenthetical note. The language is explicit: a configured hook, or an absence that cannot be established, falls to the unknown default and does not launch. The logic at
Observation (eval #5 missing hook qualifier) — Fixed. Eval #5's Gate review — exhaustive by constructionThe final gate structure at
All four cases are correct. The "cannot establish absence" path falling to the unknown default is the load-bearing piece — it makes the gate conservative on ambiguity rather than permissive. The "never route by exit status alone" principle is stated at Evals reviewAll four new evals are non-vacuous and cover the discriminating cases:
Eval #8's expectation — "An inability to establish the hook's absence is treated the same way — unknown, not clean, no launch" — correctly aligns with the skill's language. The Everything else
No issues remaining. Both prior review findings are fixed, the gate is exhaustive and fails closed on everything unrecognized, and the four evals are non-vacuous. Ready to merge. |
|
Claude finished @kyle-sexton's task in 2m 1s —— View job Security Review — PR #1728
No security issues to report. This PR changes a Markdown instruction file ( AnalysisGate structure — conservative by construction.
The only way off the no-launch default is two positively identified states. This is fail-closed throughout. WorktreeCreate hook gap — addressed in the latest commit. Injection surfaces — unchanged. Output matching and locale. Hard gate and authorization model — unchanged. |
Summary
continue-in-background's dirty-tree gate opened by runninggit status --porcelain -uallin theconsuming project. In a directory that is not a git repository — a session started in
$HOME, say —that fails with
fatal: not a git repositoryand the skill has no specified behavior. The gatespecified clean, dirty, and linked-worktree; not "no repository at all".
The gate now establishes repository status first with
git rev-parse --is-inside-work-tree, and isexhaustive by construction rather than by enumeration: two results may leave the default, and
everything else falls through to the conservative branch.
rev-parseresulttruegit status --porcelain -uallgate, unchangedWorktreeCreatehook) background sessions write to the working directory directly. The launch report states that readingThat third row is the load-bearing part, and it is why this is a little more than the issue's "one or
two sentences" — not scope drift. A gate that fails open is worse than no gate. Reading a non-zero
exit alone as "no repository" would put dubious-ownership, damaged-repository, and git-missing cases
on the launch path — exactly where the tree is most likely dirty and least likely readable. The
unknown default is deliberately wide and it catches a zero-exit case too (below).
Also here: the context-gathering block's "treat any failure as an unknown value and carry on" is now
scoped to itself. It colors the save-point and is not the gate; carrying that shrug — or that block's
non-
-uallgit statusoutput — into delivery step 1 would defeat the gate from a section that runsbefore it.
Evidence
Gates (run in the worktree against
origin/main):check-skill(the checkerscripts/check-changed-skills.shinvokes): PASS — 0 errors, 0 warnings; 195/500 lines, all 4 base-ref trigger phrases preservedscripts/validate-plugin-contracts.mjs: pass — 43 setup skills, 2123 plugin filesscripts/check-changelog-parity.sh --check-bump origin/main: pass — 0.17.16 → 0.17.17 patch bump paired with its## [0.17.17]entrymarkdownlint-cli2on both changed markdown files: 0 errorsevals.jsonvalidated againstplugins/skill-quality/reference/evals.schema.json: validHarness claim verified against current docs. The non-repo sentence asserts harness behavior, so
it was checked against https://code.claude.com/docs/en/agent-view this session rather than from
recall. That page states isolation is skipped when the working directory is not a git repository and
no
WorktreeCreatehook is configured, and that outside a repository sessions write to the workingdirectory directly — which is what the new branch relies on, including the hook qualifier.
Non-vacuousness — stated honestly.
evals.jsonin this repo is declarative and model-graded;there is no runner that asserts, so the three added cases are not executed regression tests and are
not presented as such. The discriminating argument is textual and checkable from the diff: an agent
following the pre-change SKILL.md has no branch for #5 (it runs
git status --porcelain -uallblindand hits
fatal: not a git repository) and no branch at all for #6 or #7.non-repo-directory-launches— non-repo consuming directory → launches, states the non-repo reading, STOPSunknown-tree-state-does-not-launch— dubious-ownership failure → does NOT launch. This is the case that proves the gate does not fail openno-work-tree-despite-zero-exit-does-not-launch— bare repository, exit 0 printingfalse→ does NOT launchReview
Reviewed by a fresh-context reviewer with the rationale withheld. Two IMPORTANT findings, both fixed
in
748ab3cbbefore this PR:git rev-parse --is-inside-work-treeexits 0 and printsfalseinside a bare repository or a
.gitdirectory — verified empirically by the reviewer. Theoriginal three-way enumeration left that state unspecified, which is the same defect class the
issue reports. Fixed by inverting the structure to two specified results plus a default, and
guarded by eval docs(playbook): document the local plugin development loop #7.
sat 50 lines above a gate that now reads a git failure the opposite way. Fixed by scoping that
sentence to its own block and pointing at the gate as owning failure semantics.
One finding was accepted and not fixed, as out of scope: the gate's pre-existing rationale that
uncommitted changes "would NOT carry into the launched agent's edits" does not hold when a consumer
sets
worktree.bgIsolation: "none", under which background sessions edit the working copy directly.The gate's decision stays conservative either way, so this is an inaccurate rationale rather than a
hole — untouched by this PR and worth its own issue.
Closes #929
Related
$-expansion isolation constraint the context block's shape exists to satisfy; the newgit rev-parse --is-inside-work-treecarries no$-expansion and sits in the body, not pre-compute