[Bug]: Side chats inherit the parent thread's Claude /goal, so every side-chat turn loops until the stop-hook cap#32439

opened5d agobyKKharos102

Before submitting

  • I searched existing issues and did not find a duplicate.
  • I included enough detail to reproduce or investigate the problem.

Area

apps/server

Steps to reproduce

  1. On a Claude provider instance, open a thread and set an open-ended goal, e.g. /goal Keep working through the tasks in docs/tasks.md without pausing.
  2. While that goal is active (not met, not cleared), start a side chat from the thread (/btw).
  3. Ask the side chat an unrelated question, e.g. "do we support feature X?".

Expected behavior

The side chat answers the question and its turn ends. The parent thread's goal keeps applying to the parent thread only.

Actual behavior

The side chat answers, then Claude Code's goal evaluator blocks the turn from ending: the goal is not met and the last message is "a side-chat answer". The model follows T3's side-chat framing ("Answer the user's questions; do not continue or redo the main thread's task.") and tries to stop again, so the hook blocks it again. This repeats until Claude Code's cap overrides the hook:

A hook blocked the turn from ending 9 consecutive times — overriding and ending turn.

Each block is a full model request over the forked transcript. In my case one side-chat question took about 3.5 minutes and about $8.59, almost all of it 13.4M cache-read tokens over 10 requests.

Cause: Claude Code stores an active /goal in the session transcript, as a commandRun: goal entry and goal_status attachments, and enforces it as a Stop hook. ClaudeAdapterV2.forkThread (apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.ts) forks the parent session with the SDK's session.fork (upToMessageId) and resumes the fork. The forked transcript keeps those goal entries, so the side chat inherits the parent's goal. Nothing in the side-chat path clears it. The side chat ends up with two instructions that cannot both be satisfied: the inherited goal and the side-chat framing from makeSideChatContext in ContextHandoffService.ts.

I confirmed this on main @ beaeeff4 (2026-11-07): the Claude forkThread has no goal or side-chat handling. The only goal-aware side-chat logic is in ProviderTurnStartService, which skips the side-chat context for a /goal command sent in the side chat.

Possible direction: when the fork is a side chat (lineage.sideChat), clear the inherited goal on the forked native session before its first turn. That means /goal clear for Claude. For Codex, calling thread/goal/clear on the forked thread would be a cheap defensive step. The parent's goal should be left alone.

Codex: not observed. With Codex 0.166.0, an isolated app-server probe (thread/start, optional turn, thread/goal/set with paused or active status, thread/fork, thread/goal/get on the fork) returned goal: null for the fork in every variant. However, Codex 0.166 does contain goal-inheritance code for forks (thread_fork_goal.rs), so I can't rule out other conditions.

Related, but not this bug:

  • #30614 carries goals across provider handoffs. Its description says fork goal inheritance is outside its scope.
  • #30646 stops Claude goals re-prompting while a delegated task will wake the thread. It doesn't cover side chats or forks.

Impact

Major degradation or frequent failure

Version or commit

0.1.7-nightly.20261101.4048 (the code path is unchanged in 0.1.8-nightly.20261105.4234 and on main @ beaeeff4)

Environment

Linux (Ubuntu 26.04), Claude Code 2.1.316 via the Claude Agent SDK provider, claude-opus-5-5. Codex 0.166.0 for the Codex check.

Logs or stack traces

shell
1# side-chat provider event log, condensed
2session.fork {"options":{"dir":"<project>","upToMessageId":"…"}} -> session.forked {"sessionId":"a59821d6-…"}
3query.open {"resume":"a59821d6-…", …}
4user (isSynthetic): "Stop hook feedback:\n[<parent thread's /goal condition>]: The goal is open-ended and transcript evidence shows it is not reached. … the last assistant message is a side-chat answer to a … question, and the main thread's work … was still in progress …"
5assistant: (short reply; tries to stop)
6… repeated 9 times …
7system/notification key=stop-hook-block-cap: "A hook blocked the turn from ending 9 consecutive times — overriding and ending turn. …"
8result subtype=success duration_ms=195087 total_cost_usd=8.589
9
10# forked transcript (<claude config>/projects/<project>/a59821d6-….jsonl) contains the parent's goal records:
11{"type":"attachment","attachment":{"type":"goal_status","met":false,"condition":"<parent goal>"}, …}
12{"commandRun":{"command":"goal","args":"<parent goal>"}, …}

Screenshots, recordings, or supporting files

No response

Workaround

Untested: sending /goal clear as the first message in the side chat should clear only the forked session's copy of the goal. Otherwise, avoid side chats on threads with an active Claude goal.

Synced from GitHub

Forking the whole transcript gives the side chat the thread's context in one call, which is right for questions about that thread. It breaks because the goal rides along as transcript state and the Stop hook keeps enforcing it. Fix for today: clear the goal on the fork before its first turn, as you suggest, or fork from a message before the goal entry. Disclosure: I work on ArcRoom AI. It hands a new session only the records that match its task (what was done, decided and failed) instead of a copy of the transcript: https://www.arcroomai.com/?ref=github. Is this just you, or a team whose agents share side chats?