fix(hooks): no-op pact-team-registration write in in-process teammateMode (#962)#1006
Merged
michael-wojcik merged 5 commits intoJun 21, 2026
Conversation
In in-process teammateMode all teammates share the lead's session_id, so register()'s {session_id -> name@team} write collides many-to-one and cannot recover the actor. Add an in-process self-guard: skip the registry write only on a confirmed session_id == leadSessionId (read inline from team config.json), and write normally in tmux and on every unknown/unreadable/malformed topology (fail-OPEN). Silent, reversible no-op; keys on the runtime structural signal, never a mode flag. Self-contained-leaf invariant preserved (no new shared.* imports). Refs Synaptic-Labs-AI#962.
…guard Both-modes matrix (in-process sid==leadSessionId -> no write; tmux -> write + resolvable), 6-branch fail-OPEN coverage, a non-mocked pact_context resolve_agent_name Step 3.5 seam test, and 2 real-CLI subprocess legs. Non-vacuity proven by counter-test-by-revert: a source-only guard removal flips exactly 4 RED cases (3 mode-gate + 1 seam), all others stay green. Full suite: 9361 passed, 14 skipped, 0 failed, 0 errors. Refs Synaptic-Labs-AI#962.
…nches + positive control Adds TestReadLeadSessionIdHelper: 8 direct unit tests covering every branch of the in-process guard's signal-source helper in isolation, including the empty-team branch that is unreachable through register() (short-circuited at the name/team partition) plus a positive control proving the empty-string returns are genuine misses. Strengthens the guard's non-vacuity coverage. Refs Synaptic-Labs-AI#962.
PATCH bump for the in-process pact-team-registration write-gate (Synaptic-Labs-AI#962): internal hook hardening, no user-visible surface change.
…ynaptic-Labs-AI#962) Adds a behavioral-parity test pinning session_registry._read_lead_session_id and task_claim_gate._read_lead_session_id to the same leadSessionId extraction for the same logical input (config read -> dict -> leadSessionId string, '' on any miss), making the prior 'LOGIC-PARITY' docstring claim test-enforced (resolves the review's parity-drift finding). The two copies resolve the teams dir differently (get_claude_config_dir vs the inlined _config_root); the test drives both onto one config via a shared home redirect and is scoped to segments both guards admit. Documents the benign _is_safe_team_segment-vs-is_safe_path_component space-admittance divergence as a boundary. Also fixes the counter-test docstring: removes the broken whole-file-revert alternative (errors at collection since it predates the imported helper), keeping only the working source-only two-line revert. Refs Synaptic-Labs-AI#962.
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.
Summary
Gates
pact-team-registrationso it no-ops the registry write in in-processteammateMode, where all teammates share the lead'ssession_idand the{session_id → name@team}write collides many-to-one (last-wins), unable to recover which teammate owns asession_id. The mapping is only meaningful in tmux mode (distinct process +session_idper teammate). Closes #962.What landed
pact-plugin/hooks/shared/session_registry.pyregister()): skip the registry write only on a confirmedsession_id == leadSessionId(the runtime structural signal, read inline via a new_read_lead_session_idhelper that mirrorstask_claim_gate._read_lead_session_id); write normally in tmux and fail-OPEN on every unknown / unreadable / malformed / non-dict / missing-key / unsafe-team topology. Silent, reversible no-op; keys on the structural signal, never a mode flag; zero newshared.*imports (self-contained-leaf invariant preserved).sid==leadSessionId→ no write; tmux → write + resolvable), 6-branch fail-OPEN coverage, direct_read_lead_session_idhelper unit tests + a positive control, a non-mockedpact_contextresolve-seam test, and 2 real-CLI subprocess legs. Non-vacuity proven via counter-test-by-revert: a source-only guard removal flips exactly 4 RED cases. Full suite: 9369 passed, 14 skipped, 0 failed, 0 errors.Why route-b (single-file
register()guard)A multi-agent plan-mode investigation + an adversarial feasibility workflow ruled out the alternatives:
prompt=template literal is rendered by the dispatcher before the teammate process exists, so it cannot read a runtime signal; it could only carry an authoring-time flag, violating the permanent dual-mode "structural-signal-never-a-flag" contract.session_init's SessionStart injection) was investigated and ruled NOT_VIABLE (3/3 adversarial verifiers, high confidence):SessionStartfires once per OS process, not per in-process teammate spawn — in-process teammates fireSubagentStart(→peer_inject.py), mode-exclusive — so c''s named injection point never runs on an in-process spawn, making per-in-process-teammate gating structurally impossible there.Route-b (the
register()self-guard) is the only viable, contract-legal option, and the discriminator is config-driven (readsleadSessionIdfrom teamconfig.json, never the registry), so the no-op blinds no consumer — all 3 live registry consumers are already fail-safe-to-baseline.Acceptance criteria
.teammate-registry.jsonlreaders enumerated + verified unaffected (config-driven discriminator; fail-safe consumers; non-mocked seam test) — ✅