fix(council): let the Claude Code session id win over --session

A claim made with --session <name> stored the name, but the PreToolUse gate compares the hook payload's session_id, so the gate refused every edit of the claiming session and a later release without the flag exited 3. caller() now takes CLAUDE_CODE_SESSION_ID whenever it is set and falls back to --session only for other runtimes. test_council covers a claim and release with --session under a Claude Code session (negative control against the old caller fails). Claims made with --session before this fix go stale after an hour without a heartbeat and can then be taken over.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Kevin Veen-BirkenbachandClaude Opus 5.5 committed 2026-11-03 17:57:08 +01:00
1 parent e2157998d7
commit 66e21dcd5c
2 files changed
+9 -3

No files matched your search

+4 -3
View File
@@ -4,7 +4,8 @@
Environment:
AGENTS_COUNCIL_ROOT: council directory shared by every agent on the host;
/tmp/agents-council unless set, because all agents must agree on it.
CLAUDE_CODE_SESSION_ID: the caller's session; other runtimes pass --session.
CLAUDE_CODE_SESSION_ID: the caller's session, taken over --session; other
runtimes pass --session.
Exit codes:
0 done, 2 usage error, 3 office held by a live session,
@@ -46,11 +47,11 @@ def live_sessions() -> dict[str, str | None]:
def caller(session: str | None) -> tuple[str, str]:
if session:
return session, "other"
env = os.environ.get("CLAUDE_CODE_SESSION_ID")
if env:
return env, "claude-code"
if session:
return session, "other"
print("council: no session id; pass --session", file=sys.stderr)
sys.exit(2)
+5
View File
@@ -62,6 +62,11 @@ class TestCouncil(unittest.TestCase):
self._run("s1", "claim", "work/x", "--work", "w")
self.assertEqual(self._run("s2", "takeover", "work/x", "--work", "w").returncode, 3)
def test_session_flag_cannot_hide_the_claude_code_session(self):
self._run("s1", "claim", "work/x", "--work", "w", "--session", "One")
self.assertIn('"session": "s1"', (self.root / "work/x/claim.json").read_text())
self.assertEqual(self._run("s1", "release", "work/x", "--session", "One").returncode, 0)
def test_only_the_holder_releases(self):
self._run("s1", "claim", "work/x", "--work", "w")
self.assertEqual(self._run("s2", "release", "work/x").returncode, 3)