feat(agents): mirror objectui's guard-shared-stash hook + the shared-stash warning (#5742) - #6632
Merged
Merged
Conversation
…stash warning (#5742) `refs/stash` lives in the COMMON .git dir, so every linked worktree shares one LIFO stack: the per-task worktree isolation AGENTS.md Prime Directive #11 mandates does not cover the stash. objectui#3430 (2026-08-06) had two parallel dev agents pop each other's reverse-verification stashes; pop reported success and both changesets survived only as unreachable commits. objectui landed the guard after that incident; this repo had neither the hook nor the warning (its own guard-main-checkout-bash.sh header recorded the gap). This mirrors both sides: - `.claude/hooks/guard-shared-stash.sh` + its 32-case self-test, kept case-for-case identical to objectui's verdict logic so the two guards cannot drift; only issue refs, example paths and the package name are localised. Contract preserved: blocks push/pop/save/drop/clear/branch and `stash@{N}`, allows list/show/create and apply/store pinned to a literal hex object id, fails open on unparseable shapes, escape hatch `OS_ALLOW_STASH=1`. - Wired on the existing `Bash` PreToolUse matcher in `.claude/settings.json`, ahead of guard-main-checkout-bash.sh (objectui's order); that hook's stale "this repo has no such hook" note is corrected. - AGENTS.md: the warning + collision-free replacements next to the worktree rule, and one clause inside Prime Directive #11. - `.claude/agents/os-dev.md`: the reverse-verification section now prescribes `git checkout origin/main -- <path>` / patch file / temp commit and forbids bare `git stash` — the producer-side fix for the recipe that caused #3430. - CLAUDE.md: inlined as a fourth never-miss rule (same bar as the other three: missing it corrupts other agents' work). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01F8q5J1MQyocgtNspb15fSn
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
os-project-manager
marked this pull request as ready for review
August 8, 2026 07:49
os-project-manager
enabled auto-merge
August 8, 2026 07:49
This was referenced Aug 8, 2026
This was referenced Aug 8, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #5742
Why
refs/stashand its reflog live in the common.gitdirectory, so every linkedworktree of a repo shares one LIFO stack. The per-task worktree isolation AGENTS.md
Prime Directive #11 mandates — and
guard-main-checkout*.shenforce — covers the workingtree and HEAD, and does not cover the stash. Two agents stashing at the same time swap
entries: one
poprestores the other's changes into your worktree while yours stay on thestack,
popreports success, and a followinggit add -Acommits their half-finishedwork into your PR.
That is not hypothetical: objectui#3430 (2026-08-06) hit two parallel dev agents in the
middle of reverse verification — the workflow every dev agent runs, which is exactly why
the collision window is wide. Both changesets survived only as unreachable commits whose
SHAs were still in scrollback; once the stack empties,
refs/stashandlogs/refs/stashare gone (
git reflog refs/stashanswersfatal: ambiguous argument) and agit gcinbetween makes the loss permanent.
objectui landed a guard after its incident. This repo had neither the guard nor the
warning — a gap its own
guard-main-checkout-bash.shheader had recorded in prose("objectui additionally runs guard-shared-stash.sh on the same matcher; this repo has no
such hook"). This PR mirrors the objectui side here.
Premise verification (before implementing)
Both PM assumptions hold at
origin/main:git ls-tree -r --name-only origin/main -- .claude/listed
guard-main-checkout.sh,guard-main-checkout-bash.shand its self-test, andnothing else;
git grep -i stash origin/main -- AGENTS.md CLAUDE.md .claude/returnedexactly one hit — the stale note in
guard-main-checkout-bash.sh:17quoted above.origin/mainand are theincident-hardened versions (objectui#3430 acceptance comment records the 32-case matrix
and its reverse verification). Read via
git -C /home/user/objectui show origin/main:.claude/hooks/guard-shared-stash.sh, notfrom its working tree. No open issue in objectui records a gap in the hook.
.claude/settings.json) and a PR can carry it — nowiring gap to report.
What landed
.claude/hooks/guard-shared-stash.sh— mirrored from objectui. The verdict logic(
split_segments/check_segment) is kept case-for-case identical so the two repos'guards cannot drift; only issue references, example paths and the package name in the
self-test are localised. The contract is preserved exactly:
push/pop/save/drop/clear/branch,bare
git stash, andstash@{N}positions (a position in a stack you do not own);git stash list/show/create, andapply/storepinned to aliteral hex object id (the recovery path used to repair the incident);
bash -c …,xargs), quote-aware so thatwriting or grepping about the ban never trips the ban (objectstack#4890's lesson);
guard-main-checkout.sh: 0 allow, 2 block; escape hatchOS_ALLOW_STASH=1.Localised beyond objectui's copy: the recommended replacement list leads with
git checkout origin/main -- PATH, which is the recipe the 2026-08-06 routing commenton this issue named for reverse verification.
.claude/hooks/guard-shared-stash.selftest.sh— the 32-case matrix, one-for-onewith objectui's, with objectstack paths/packages.
Wiring — registered in
.claude/settings.jsonon the existingBashPreToolUse matcher, ahead of
guard-main-checkout-bash.sh(objectui's order), i.e.identically to how this repo already registers its guards. The stale
"this repo has no such hook" note in
guard-main-checkout-bash.shis corrected in thesame commit — guard-main-checkout.sh 同样只挂 Edit|Write|NotebookEdit,本仓经 Bash 写文件(sed -i / tee / 重定向)可绕过 worktree 强制 #5790's closing observation is now closed.
AGENTS.md— the warning next to the worktree rule: a block in the Multi-agentworking discipline section (mechanism, the objectui#3430 incident, the four
collision-free replacements, the hook and its escape hatch), plus one clause inside
Prime Directive Migrate documentation site to Fumadocs with monorepo structure and shared content #11 so a reader who never reaches the section still learns that the
stash is the one thing a worktree does not isolate. Deliberately scoped to that area:
objectstack#5981 also edits
AGENTS.mdand should land cleanly on top..claude/agents/os-dev.md— the producer-side fix. The reverse-verificationsection now prescribes
git checkout origin/main -- PATH, a patch file, or a tempcommit, and forbids bare
git stash. This is the recipe that caused objectui#3430;fixing it in the agent definition beats patching it per-dispatch.
CLAUDE.md— inlined as a fourth never-miss rule (heading count updatedthree → four). Call and reasoning: this file states its own bar as "missing any one of
them wastes or corrupts other agents' work", and this defect is the only one of the set
with a measured instance of exactly that — two agents' in-flight changes swapped in
one morning. objectui made the same call in its
CLAUDE.mdafter the incident, soinlining also keeps the two repos' loaded-every-session rule sets aligned. The full
mechanism stays in
AGENTS.md; the inline copy is the rule plus the replacements.Self-test evidence (the change gate for this hook)
bash -nclean on both files;.claude/settings.jsonparses (JSON.parse); both newfiles committed mode
100755.Reverse verification — direction predicted before running
Prediction: neutering
check_segmentto always return 0 (allow) must turn exactly the15 block cases red and leave the 17 allow cases green — the usual "red" direction,
because every block case is a positive assertion about the one verdict being removed.
Run on a scratch copy (the committed hook untouched):
15 red / 17 green, as predicted. The matrix cannot go green on a guard that does not
guard.
Gates
Enumerated from
.github/workflows/lint.yml(everypnpmstep, in order) and run on thecommitted tree, not from memory. All green:
pnpm lint(exit 0) and all 30check:*steps:slot-lookup,query-options-erasure,verify-stand-in,nul-bytes,doc-authoring,docs-audit-scope,role-word,quick-reference-counts,adr-anchors,org-identifier,authz-resolver,service-providers,route-envelope,error-code-casing,wildcard-fallthrough,meta-type-normalized,init-service-contract,durability-log-level,startup-registry-verdict,objectui-changeset,release-notes,release-body,node-version,workflow-status-functions,shard-attestation,published-files,engine-double-contract,resume-authority-declared,merge-driver,spec-parsed-alias.check:type-check-coverage,check:driver-conformance,check:stall-guard,check:skill-frame-sync,check:skill-compatibility,check:i18n,check:i18n-coverage,spec exec tsc --noEmit, and the@objectstack/specgatescheck:generated --reconcile-only,skill-docs,spec-changes,upgrade-guide,authorable-surface,docs,skill-refs,react-blocks,api-surface,exported-any,dual-source-exports,skill-examples, plus@objectstack/lint check:doc-formula-expressions.check:skill-frame-syncis the one that reads.claude/agents/os-dev.md(the internal dev-agent copy of the escalation frame); green — this PR edits the
reverse-verification section, not the frame.
(
api-surface,exported-any,dual-source-exports,skill-examples,doc-formula-expressions,i18n*); afterturbo run build --filter='./packages/*' --filter='./packages/*/*'all six are green.turbo run typecheck/ examples / downstream-contract steps.This PR changes zero
.ts/.tsx/.jsfiles (markdown, shell, one JSON), so thosesteps have no input from it; CI runs them.
node scripts/check-nul-bytes.mjsOK, plus a self-scan beyond the gate(
grep -naP '[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]') over every touched file: no hits.Changeset decision:
skip-changeset, no changeset fileThis PR touches
.claude/**,AGENTS.mdandCLAUDE.mdonly. No published packagechanges, so there is nothing for
changesetsto version: a changeset here would have toname a package it does not touch and would put an agent-protocol edit into that package's
CHANGELOG and the release notes. That is the exact case the dev-agent definition calls
out ("a
.claude/-only PR releases nothing and writes no changeset"), so the label is thehonest declaration and it is applied on this PR rather than merely declared here.
Observation, not fixed here
objectui's copy of the hook has a stale header line —
# Self-test (26 cases …)while itsmatrix has been 32 cases since it landed (the objectui#3430 acceptance comment says 32).
The mirrored copy in this PR states 32, which is the measured number. The objectui-side
drift is cosmetic and belongs to that repo; noted rather than inherited silently.
🤖 Generated with Claude Code
https://claude.ai/code/session_01F8q5J1MQyocgtNspb15fSn
Generated by Claude Code