Skip to content

fix(hooks): CLAUDE_CODE_FORK_SUBAGENT is a feature flag, not a subagent marker — disables 9 hooks on every install - #1942

Open
Autodidact-med wants to merge 1 commit into
danielmiessler:mainfrom
Autodidact-med:fix/fork-subagent-flag-disables-hooks
Open

fix(hooks): CLAUDE_CODE_FORK_SUBAGENT is a feature flag, not a subagent marker — disables 9 hooks on every install#1942
Autodidact-med wants to merge 1 commit into
danielmiessler:mainfrom
Autodidact-med:fix/fork-subagent-flag-disables-hooks

Conversation

@Autodidact-med

Copy link
Copy Markdown

Problem

settings.system.json sets CLAUDE_CODE_FORK_SUBAGENT: "1" in the global env block, because that variable is the feature-enable flag for forked subagents (per the Claude Code changelog: "Forked subagents can now be enabled on external builds by setting CLAUDE_CODE_FORK_SUBAGENT=1").

hooks/lib/subagent.ts also treats that same variable as a runtime marker identifying a subagent.

Because the flag is set globally, isSubagentContext() returns true in every session, including the main one. All nine consumer hooks that gate on it silently no-op.

Impact

On a default install, these stop firing entirely:

MemoryReviewFire, LoadMemory, MemoryTurnStart, MemoryDeltaSurface, ISASync, ConfigEvalFire, SystemChangeSurface, LoadContext, KittyEnvPersist

Nothing errors. The autonomic memory loop simply stops: review-state.json, MEMORY/STATE/memory-inject, and the delta-surface heartbeat stop being written, while every hook not behind this guard keeps working normally. That asymmetry is the only visible symptom.

Observed on a 7.40.4 install: the memory review loop was dead for five days before anyone noticed, and the hot layer stopped accumulating entries.

Why the flag cannot work as a marker

A forked subagent inherits its parent's environment. Even without the global env entry, the flag would be present in both parent and fork, so it cannot distinguish them in principle.

Fix

Remove CLAUDE_CODE_FORK_SUBAGENT from the union in isSubagentContext(). The explicit markers (CLAUDE_AGENT_TYPE, CLAUDE_CODE_SUBAGENT_NAME, CLAUDE_CODE_SUBAGENT_TYPE, CLAUDE_AGENT_SDK, and the /.claude/Agents/ path test) are unaffected.

Trade-off

This removes the mitigation added for the fork double-context-injection issue referenced in the original comment. A fork may again re-inject main-session context. That is a duplicated context line; the current behaviour is a dead memory subsystem on every install. If fork detection is still wanted, it needs a marker the harness sets only in forks, which this flag is not.

Verification

With the flag removed, isSubagentContext() returns false in a main session, the Stop hook resumes writing per-session state and review-state.json, and the reviewer fires on its normal cadence again.

settings.system.json sets CLAUDE_CODE_FORK_SUBAGENT=1 globally in env, because
it is the feature-enable flag for forked subagents. isSubagentContext() also
treated it as a runtime marker, so it returned true in EVERY session and all
nine consumer hooks silently no-oped.

Impact on a default install: memory review, memory injection, turn-start
memory, delta surface, ISA sync, config-eval fire, system-change surface, and
context loading all stop firing. Nothing errors; the affected state files just
stop being written.

A fork inherits its parent's environment, so the flag cannot distinguish a fork
from its parent even in principle.
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.

1 participant