fix(launch): count launch depth instead of refusing the first nested agent - #119
Merged
Conversation
…agent
`CUE_LAUNCHING` was the string "1" and the guard tripped on it, which
conflates the two things re-entry can mean.
A shim LOOP — cue resolving the agent binary back to its own shim — re-
enters without bound and has to be stopped. A NESTED launch — an agent,
or a tool it runs, invoking `claude` for a subtask such as an AI code
review — re-enters exactly once and is legitimate. The flag is inherited
by the entire process tree under a launched agent, so the old guard
killed the second case too, with a message blaming PATH ordering:
cue: shim recursion detected — check PATH ordering (...)
That message sends you hunting through PATH for a fault that is not
there. It cost a merge gate today: the AI-review provider shelled out to
`claude` from inside a cue-launched session and was refused.
Callers that knew about it worked around it by hand — `failures.ts`
deletes the variable before spawning, and the headless-gif-demo skill
tells you to `unset` it. A caller that did not know simply failed.
Counting separates the cases: a loop climbs to the cap in milliseconds,
honest nesting stays shallow. `CUE_LAUNCHING` now carries a depth, the
guard trips at 3, and the message names both causes. A non-numeric value
from an older cue still reads as depth 1.
Verified from inside a cue-launched session, where CUE_LAUNCHING=1 is
inherited and the shipped cue refuses:
installed cue → "shim recursion detected"
patched cue → launches ("profile": "core", "agent": "claude-code")
patched cue at CUE_LAUNCHING=3 → still refuses, with both causes named
Full suite 3019 pass / 1 skip / 0 fail, tsc --noEmit exit 0, biome clean.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
NagyVikt
added a commit
that referenced
this pull request
Aug 5, 2026
announceTmuxProfile() gated only on being inside tmux, so every cue launch
reached it — including the short-lived `--print` skill-selector cue spawns on
each session start. That helper inherits TMUX_PANE from the session it is
helping, so it set @cue_* on that pane and then, seconds later, its exit
handler unset all eight. The interactive session kept running with its badge
wiped from the pane border: the profile name and icon simply vanished.
Measured on this machine: 1 of 12 live panes still had @cue_profile_name set.
Two guards, because they cover different cases:
- ownsPaneBadge() adds a TTY requirement. Verified against live processes:
all seven interactive launchers write to /dev/pts/N, while the classifier
is spawned with stdio ["ignore", "pipe", "ignore"], so a pipe is exactly
what distinguishes a helper from the session the user is looking at.
- The exit sweep now reads the options back and unsets only those still
holding the values it wrote. A nested launch — supported since #119 — has
a TTY too, so the TTY guard alone would not stop it from clearing an outer
session's badge on exit. One extra spawn, batched like the set path.
A failed read-back leaves the options in place: leaking a stale badge is
recoverable on the next launch, clearing a live one is not.
Co-authored-by: NagyVikt <nagy.viktordp@gmail.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
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.
Unblocks any tool that shells out to
claudefrom inside a cue-launched session — an AI code-review provider, most immediately, which was refused mid-merge-gate today.The conflation
CUE_LAUNCHINGwas the string"1"and the guard tripped on it. Re-entry can mean two different things:claudefor a subtaskThe flag is inherited by the whole process tree under a launched agent, so the second case was refused too — with a message pointing at a fault that isn't there:
Callers that knew worked around it by hand:
failures.tsdeletes the variable before spawning, and theheadless-gif-demoskill instructsunset CUE_LAUNCHING …. A caller that didn't know just failed.The fix
Count instead of flag. A loop climbs to the cap in milliseconds; honest nesting stays shallow.
CUE_LAUNCHINGcarries a depth; each launch writesdepth + 1into the child envMAX_LAUNCH_DEPTH = 3— one nested agent is fine, a loop still dies almost instantlyVerification
From inside a cue-launched session, where
CUE_LAUNCHING=1is inherited and the shipped cue refuses:shim recursion detected"profile": "core","agent": "claude-code"CUE_LAUNCHING=3Three e2e cases replace the single old one: trips at the cap, allows one nested launch, and reads a legacy non-numeric marker as depth 1.
Full suite 3019 pass, 1 skip, 0 fail.
tsc --noEmitexit 0.biome lintclean.🤖 Generated with Claude Code