feat(hook): let a non-git surface declare its identity via CLAUDE_COORD_REPO
0.6.0 removed the working-directory fallback, which was right, but it left every non-git working surface with nothing to derive from. The read path declines silently by design - the hook must never fail a session - so such a surface simply stops seeing its inbox: no error, no exit code, nothing. That is the same loss-looks-like-normal shape 0.6.0 set out to remove, reintroduced one layer up. The hook now forwards CLAUDE_COORD_REPO verbatim as --repo. This is not the fallback returning, and the distinction is the whole point: the fallback GUESSED a name from wherever the session happened to stand, while a declaration is written down in that directory's settings, readable back, and deletable. Identity stays a choice someone made. Forwarding as --repo rather than reimplementing anything means it inherits every engine rule, including that an explicit override never claims <repo>/.origin - otherwise a surface borrowing a name would steal the claim from the checkout that owns it. Both halves are pinned by tests, and the shared test helper now deletes CLAUDE_COORD_REPO unless a test asks for it, so a value set globally later cannot silently satisfy the tests that prove the hook resolves nothing on its own. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01U6EixQo6hpoRCVtiAXdnFs
This commit is contained in:
parent
95c24cd034
commit
28e7cb42cd
7 changed files with 91 additions and 18 deletions
|
|
@ -23,15 +23,25 @@ try {
|
|||
const pluginRoot = process.env.CLAUDE_PLUGIN_ROOT
|
||||
|| join(dirname(fileURLToPath(import.meta.url)), '..', '..');
|
||||
|
||||
// No --repo, and no identity resolution here. This used to resolve the repo
|
||||
// itself and fall back to process.cwd(), which made it a fourth independent
|
||||
// copy of the identity rule - and the only one that runs in production, so
|
||||
// the engine's guards were bypassed exactly where they mattered. The engine
|
||||
// runs in this same cwd and derives the identity from git alone; passing
|
||||
// --repo would also mark the read as an explicit override and suppress the
|
||||
// mailbox-collision check. Boundary rule: no mailbox logic lives in the hook.
|
||||
// No identity resolution here. This used to resolve the repo itself and fall
|
||||
// back to process.cwd(), which made it a fourth independent copy of the
|
||||
// identity rule - and the only one that runs in production, so the engine's
|
||||
// guards were bypassed exactly where they mattered. The engine runs in this
|
||||
// same cwd and derives the identity from git alone.
|
||||
//
|
||||
// CLAUDE_COORD_REPO is the one exception, and it is a DECLARATION rather than
|
||||
// a derivation: a working surface that is not a git repo (~/repos, $HOME) has
|
||||
// nothing to derive from, so the read path declines silently and the surface
|
||||
// loses its injection with no error - loss wearing the shape of normal. The
|
||||
// operator sets this in that directory's settings to say which mailbox the
|
||||
// surface owns. It is not the pwd fallback returning: the fallback guessed,
|
||||
// this is written down, readable back, and deletable. Forwarded verbatim as
|
||||
// --repo, so it inherits the engine's rules - including that an explicit
|
||||
// override never claims .origin. Boundary rule holds: no mailbox logic here.
|
||||
const declared = process.env.CLAUDE_COORD_REPO;
|
||||
const script = join(pluginRoot, 'scripts', 'coord-inbox.sh');
|
||||
const inbox = execFileSync('bash',
|
||||
[join(pluginRoot, 'scripts', 'coord-inbox.sh')],
|
||||
declared ? [script, '--repo', declared] : [script],
|
||||
{ stdio: ['ignore', 'pipe', 'ignore'], encoding: 'utf8' });
|
||||
|
||||
emit(inbox.trim() ? '== Repo coordination (unread messages) ==\n' + inbox : '');
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue