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
Conversation
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.
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.
Problem
settings.system.jsonsetsCLAUDE_CODE_FORK_SUBAGENT: "1"in the globalenvblock, 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 settingCLAUDE_CODE_FORK_SUBAGENT=1").hooks/lib/subagent.tsalso treats that same variable as a runtime marker identifying a subagent.Because the flag is set globally,
isSubagentContext()returnstruein 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,KittyEnvPersistNothing 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
enventry, the flag would be present in both parent and fork, so it cannot distinguish them in principle.Fix
Remove
CLAUDE_CODE_FORK_SUBAGENTfrom the union inisSubagentContext(). 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()returnsfalsein a main session, the Stop hook resumes writing per-session state andreview-state.json, and the reviewer fires on its normal cadence again.