Skip to content

pact-session-context.json contains stale fixture data; not updated at session start #648

@michael-wojcik

Description

@michael-wojcik

Background

During PR #641 wrap-up (running Skill("PACT:unwatch-inbox")), the skill's lead-session guard refused to call TaskStop on its own Monitor task because ~/.claude/pact-session-context.json contained stale fixture data:

{
  "team_name": "pact-aabb1122",
  "session_id": "aabb1122-0000-0000-0000-000000000000",
  "project_dir": "/Users/mj/Sites/test-project",
  "started_at": "2026-04-03T03:33:14.954845+00:00"
}

The actual current session was pact-ec28101d (from CLAUDE.md Current Session block, from team config leadSessionId, from inbox file paths). The mismatch caused the unwatch-inbox guard to skip TaskStop on its own legitimate Monitor — leaving an orphan Monitor task running until session-end.

Fixture indicators:

  • session_id is fake UUID (aabb1122-0000-0000-0000-000000000000, not a real format)
  • project_dir is /Users/mj/Sites/test-project (not the actual /Users/mj/Sites/collab/PACT-prompt)
  • started_at is 2026-04-03 (over a month before the session that's reading it)

Symptom & impact

  1. Skill guards refuse legitimate operations: any skill that uses pact-session-context.json for team_name/session_id validation (the unwatch-inbox skill, possibly others) will skip privileged operations on the actual current session.
  2. Cross-session-LLM speculation footgun: an editing LLM seeing the mismatch might "fix" by bypassing the guard, which is exactly the failure mode the guard is designed to prevent against actual cross-session attack.
  3. Audit confusion: any tooling reading pact-session-context.json for diagnostics gets stale data.

Likely cause

pact-session-context.json is a singleton file at the user's home directory. It is supposed to be updated authoritatively at session start (probably by session_init.py or similar). Either:

(a) The update path doesn't fire reliably — possibly conditional on certain hook paths that this session didn't traverse; OR
(b) The file is written once and never updated — fixture data from a test session leaks into all subsequent sessions.

Investigation needed

  • Audit session_init.py and any other writer for pact-session-context.json to determine the update path.
  • Confirm whether the file SHOULD be authoritatively updated on every session start, or whether it's intentionally singleton-static (and what the intended semantics are).
  • If the update path exists but is gated, identify the gating condition and why this session didn't traverse it.
  • If the update is missing, design and implement the update path.

Proposal

Option A — authoritative update at session_init

# pact-plugin/hooks/session_init.py (or similar)
def _write_session_context(team_name: str, session_id: str, project_dir: str):
    ctx_path = Path.home() / ".claude" / "pact-session-context.json"
    ctx = {
        "team_name": team_name,
        "session_id": session_id,
        "project_dir": project_dir,
        "started_at": datetime.now(timezone.utc).isoformat(),
    }
    tmp = ctx_path.with_suffix(".json.tmp")
    tmp.write_text(json.dumps(ctx))
    os.replace(tmp, ctx_path)  # atomic rename

Fires on every session start. Atomic-rename so concurrent reads don't see torn state.

Option B — per-session context (not singleton)

# Move pact-session-context.json into the session-specific directory:
# ~/.claude/pact-sessions/{slug}/{session_id}/pact-session-context.json

Eliminates the singleton-staleness class entirely. Skills look up via pact_context.get_session_context() which knows the session_id. But this is invasive — many tools may rely on the singleton path.

Acceptance criteria

  • Root cause identified (where pact-session-context.json should be updated and why it isn't).
  • Update path implemented and tested.
  • Test asserts pact-session-context.json reflects current session's team_name + session_id after session_init fires.
  • Manual verification: a fresh session updates the file from any prior state.

Cross-references

  • PR Restore session-startup ritual (#628) #641 wrap-up: observed during Skill("PACT:unwatch-inbox") invocation.
  • unwatch-inbox skill lead-session-guard logic.
  • watch-inbox skill lead-session-guard logic (same guard, same reliance on pact-session-context.json).
  • Possibly relates to pact_context.py accessor.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions