Skip to content

fix(hooks): no-op pact-team-registration write in in-process teammateMode (#962)#1006

Merged
michael-wojcik merged 5 commits into
Synaptic-Labs-AI:mainfrom
michael-wojcik:fix/team-registration-inprocess-gate
Jun 21, 2026
Merged

fix(hooks): no-op pact-team-registration write in in-process teammateMode (#962)#1006
michael-wojcik merged 5 commits into
Synaptic-Labs-AI:mainfrom
michael-wojcik:fix/team-registration-inprocess-gate

Conversation

@michael-wojcik

Copy link
Copy Markdown
Collaborator

Summary

Gates pact-team-registration so it no-ops the registry write in in-process teammateMode, where all teammates share the lead's session_id and the {session_id → name@team} write collides many-to-one (last-wins), unable to recover which teammate owns a session_id. The mapping is only meaningful in tmux mode (distinct process + session_id per teammate). Closes #962.

What landed

  • Guard (pact-plugin/hooks/shared/session_registry.py register()): skip the registry write only on a confirmed session_id == leadSessionId (the runtime structural signal, read inline via a new _read_lead_session_id helper that mirrors task_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 new shared.* imports (self-contained-leaf invariant preserved).
  • Tests: the standing both-modes matrix (in-process sid==leadSessionId → no write; tmux → write + resolvable), 6-branch fail-OPEN coverage, direct _read_lead_session_id helper unit tests + a positive control, a non-mocked pact_context resolve-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.
  • Version: PATCH bump 4.4.34 → 4.4.35 (internal hook hardening; no user-visible surface change).

Why route-b (single-file register() guard)

A multi-agent plan-mode investigation + an adversarial feasibility workflow ruled out the alternatives:

  • Route-a (gate the directive across the ~20 spawn-prompt sites) is contract-illegal — a static 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.
  • Route-c' (relocate the directive into session_init's SessionStart injection) was investigated and ruled NOT_VIABLE (3/3 adversarial verifiers, high confidence): SessionStart fires once per OS process, not per in-process teammate spawn — in-process teammates fire SubagentStart (→ 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 (reads leadSessionId from team config.json, never the registry), so the no-op blinds no consumer — all 3 live registry consumers are already fail-safe-to-baseline.

Acceptance criteria

  • AC1 in-process no longer performs an un-fulfillable registration (silent no-op) — ✅
  • AC2 tmux registration unchanged — ✅
  • AC3 all .teammate-registry.jsonl readers enumerated + verified unaffected (config-driven discriminator; fail-safe consumers; non-mocked seam test) — ✅
  • AC4 keys on a runtime structural signal, validated under BOTH modes (standing matrix) — ✅
  • AC5 no spawn-prompt dispatch site touched (route-b) — ✅ N/A by design

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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

pact-team-registration instruction sent to teammates in in-process teammateMode where {session_id→name} mapping is ambiguous

1 participant