Problem
Three environment variables that PACT hooks depend on are consistently empty across all sessions:
CLAUDE_CODE_TEAM_NAME — always empty
CLAUDE_CODE_AGENT_NAME — always empty
CLAUDE_SESSION_ID — always empty
Root Cause (Confirmed)
These variables are PACT inventions — Claude Code was never designed to provide them.
Deep investigation (session pact-1b41631e, 2026-04-02) confirmed:
- Claude Code only provides 5 env vars to hooks:
CLAUDE_PROJECT_DIR, CLAUDE_PLUGIN_ROOT, CLAUDE_PLUGIN_DATA, CLAUDE_CODE_REMOTE, CLAUDE_ENV_FILE
- Context like
session_id, agent_id, agent_type is provided via stdin JSON, not env vars
- GitHub issue #9567 confirms Claude Code has "not planned" env var context delivery
- PACT hooks were written assuming an API that doesn't exist
What Claude Code Actually Provides
Env vars (official, all work):
| Variable |
Available To |
Purpose |
CLAUDE_PROJECT_DIR |
All hooks |
Project root |
CLAUDE_PLUGIN_ROOT |
All hooks |
Plugin install directory |
CLAUDE_PLUGIN_DATA |
All hooks |
Plugin's persistent data dir |
CLAUDE_CODE_REMOTE |
All hooks |
"true" in web environments |
CLAUDE_ENV_FILE |
SessionStart, CwdChanged, FileChanged |
File path for persisting env vars |
Stdin JSON (all hooks receive):
{
"session_id": "abc123",
"agent_id": "unique-id",
"agent_type": "Explore",
"tool_name": "Edit",
"cwd": "/current/dir",
"hook_event_name": "PostToolUse"
}
What PACT Hooks Expect vs. What Exists
| PACT Expects (env var) |
Actually Available |
Where |
CLAUDE_CODE_TEAM_NAME |
Nowhere — not in env vars or stdin |
Must self-provision |
CLAUDE_CODE_AGENT_NAME |
agent_id + agent_type |
Stdin JSON (not the human-readable name) |
CLAUDE_SESSION_ID |
session_id |
Stdin JSON — already there, hooks read the wrong source |
Impact: Silently Dead Features
Multiple PACT features have been non-functional since creation, passing unit tests (which mock the env vars) but never working in production:
| Feature |
Hook |
Status |
Since |
| Environment drift detection |
file_tracker.py |
Dead — never tracked any agent edits |
Creation |
| Teachback enforcement |
teachback_check.py |
Dead — can't identify agents |
Creation |
| Peer context injection |
peer_inject.py |
Dead — exits early without team name |
Creation |
| Per-session file tracking |
track_files.py |
Broken — 1196 entries across all sessions in unknown.json |
Creation |
| S2 drift self-exclusion |
s2_drift_check.py (PR #340) |
Degraded — falls back to "unknown" |
PR #340 |
Evidence
# Env vars empty for both main process and teammates
$ echo "CLAUDE_CODE_TEAM_NAME=$CLAUDE_CODE_TEAM_NAME"
CLAUDE_CODE_TEAM_NAME=
# track_files.py has accumulated 1196 entries in unknown.json across ALL sessions
$ wc -l ~/.claude/pact-memory/session-tracking/unknown.json
# Contains entries from every session, proving CLAUDE_SESSION_ID has NEVER been set
# PostToolUse hooks DO fire (track_files.py recorded teammate edits)
# but env-var-dependent hooks exit early with no output
Recommended Fix: CLAUDE_ENV_FILE Self-Provisioning
Claude Code provides CLAUDE_ENV_FILE — an official mechanism where SessionStart hooks can write KEY=VALUE pairs that the platform persists as env vars for all subsequent hooks in the session.
Architecture
SessionStart (session_init.py — already runs, already knows team_name + session_id)
├── Reads session_id from stdin JSON ✓ (already does this)
├── Computes team_name ✓ (already does this)
└── NEW: Writes to CLAUDE_ENV_FILE:
PACT_TEAM_NAME=pact-1b41631e
PACT_SESSION_ID=1b41631e-66e9-...
All subsequent hooks (PostToolUse, PreToolUse, etc.)
└── Read PACT_TEAM_NAME and PACT_SESSION_ID from env vars
(platform loads them from CLAUDE_ENV_FILE automatically)
What This Fixes
| Variable |
Source |
Fix |
| Team name |
CLAUDE_ENV_FILE (written by session_init.py) |
Rename from CLAUDE_CODE_TEAM_NAME to PACT_TEAM_NAME |
| Session ID |
CLAUDE_ENV_FILE OR stdin JSON session_id |
Rename from CLAUDE_SESSION_ID to PACT_SESSION_ID, also read from stdin |
| Agent name |
Stdin JSON agent_id → lookup in team config |
Map agent_id to name via ~/.claude/teams/{team}/config.json members array |
What This Doesn't Fix
Agent name (CLAUDE_CODE_AGENT_NAME): The human-readable agent name (e.g., "backend-coder-1") is not available via env vars or stdin JSON. Stdin provides agent_id (UUID) and agent_type (e.g., "pact-backend-coder"), but not the name parameter passed to the Agent tool.
Options for agent identity:
A) Map agent_id → name via team config — read ~/.claude/teams/{team}/config.json which has a members array with {name, agentId, agentType}
B) Use agent_type as proxy — less specific but always available in stdin
C) Self-provision via hook — SubagentStart hook writes agent name to a per-agent file, subsequent hooks read it
Implementation Scope
- session_init.py: Write
PACT_TEAM_NAME and PACT_SESSION_ID to CLAUDE_ENV_FILE
- All affected hooks: Replace
os.environ.get("CLAUDE_CODE_TEAM_NAME") with os.environ.get("PACT_TEAM_NAME") (or read from stdin where available)
- All affected hooks: Replace
os.environ.get("CLAUDE_SESSION_ID") with os.environ.get("PACT_SESSION_ID") or read from stdin session_id
- Agent identity hooks: Implement agent_id → name lookup from team config (option A)
- Tests: Update all mocked env vars to match new names
Migration
- Old env var names → new PACT-prefixed names
- Hooks that currently read env vars → read stdin JSON first, env var fallback
- Pattern:
input_data.get("session_id") or os.environ.get("PACT_SESSION_ID", "")
Related
Problem
Three environment variables that PACT hooks depend on are consistently empty across all sessions:
CLAUDE_CODE_TEAM_NAME— always emptyCLAUDE_CODE_AGENT_NAME— always emptyCLAUDE_SESSION_ID— always emptyRoot Cause (Confirmed)
These variables are PACT inventions — Claude Code was never designed to provide them.
Deep investigation (session pact-1b41631e, 2026-04-02) confirmed:
CLAUDE_PROJECT_DIR,CLAUDE_PLUGIN_ROOT,CLAUDE_PLUGIN_DATA,CLAUDE_CODE_REMOTE,CLAUDE_ENV_FILEsession_id,agent_id,agent_typeis provided via stdin JSON, not env varsWhat Claude Code Actually Provides
Env vars (official, all work):
CLAUDE_PROJECT_DIRCLAUDE_PLUGIN_ROOTCLAUDE_PLUGIN_DATACLAUDE_CODE_REMOTECLAUDE_ENV_FILEStdin JSON (all hooks receive):
{ "session_id": "abc123", "agent_id": "unique-id", "agent_type": "Explore", "tool_name": "Edit", "cwd": "/current/dir", "hook_event_name": "PostToolUse" }What PACT Hooks Expect vs. What Exists
CLAUDE_CODE_TEAM_NAMECLAUDE_CODE_AGENT_NAMEagent_id+agent_typeCLAUDE_SESSION_IDsession_idImpact: Silently Dead Features
Multiple PACT features have been non-functional since creation, passing unit tests (which mock the env vars) but never working in production:
file_tracker.pyteachback_check.pypeer_inject.pytrack_files.pyunknown.jsons2_drift_check.py(PR #340)Evidence
Recommended Fix: CLAUDE_ENV_FILE Self-Provisioning
Claude Code provides
CLAUDE_ENV_FILE— an official mechanism where SessionStart hooks can write KEY=VALUE pairs that the platform persists as env vars for all subsequent hooks in the session.Architecture
What This Fixes
CLAUDE_ENV_FILE(written by session_init.py)CLAUDE_CODE_TEAM_NAMEtoPACT_TEAM_NAMECLAUDE_ENV_FILEOR stdin JSONsession_idCLAUDE_SESSION_IDtoPACT_SESSION_ID, also read from stdinagent_id→ lookup in team config~/.claude/teams/{team}/config.jsonmembers arrayWhat This Doesn't Fix
Agent name (
CLAUDE_CODE_AGENT_NAME): The human-readable agent name (e.g., "backend-coder-1") is not available via env vars or stdin JSON. Stdin providesagent_id(UUID) andagent_type(e.g., "pact-backend-coder"), but not thenameparameter passed to the Agent tool.Options for agent identity:
A) Map agent_id → name via team config — read
~/.claude/teams/{team}/config.jsonwhich has amembersarray with{name, agentId, agentType}B) Use agent_type as proxy — less specific but always available in stdin
C) Self-provision via hook — SubagentStart hook writes agent name to a per-agent file, subsequent hooks read it
Implementation Scope
PACT_TEAM_NAMEandPACT_SESSION_IDtoCLAUDE_ENV_FILEos.environ.get("CLAUDE_CODE_TEAM_NAME")withos.environ.get("PACT_TEAM_NAME")(or read from stdin where available)os.environ.get("CLAUDE_SESSION_ID")withos.environ.get("PACT_SESSION_ID")or read from stdinsession_idMigration
input_data.get("session_id") or os.environ.get("PACT_SESSION_ID", "")Related
s2_drift_check.pyusesCLAUDE_CODE_AGENT_NAMEwith "unknown" fallback