fix: Claude Stop-hook continuations no longer hide the answer#27505

closed1
main
GitHub fork: fix/claude-stop-hook-answer-visible
opened11d agobyAAppLoidx
View on GitHubTwo-way sync enabledPR last imported 11d ago

Problem

With the Claude provider, a user's Stop hook can block the end of a turn. Claude then replies to the hook in the same run, so the run ends with two assistant messages: the real answer, then a short reply to the hook. After the run settles, the timeline keeps only the last message visible and folds the real answer under "Worked for". To the user, the answer looks like it disappeared.

Repro: add any Stop hook that blocks once (for example a script that returns {"decision":"block","reason":"..."}), ask Claude a question, and wait for the run to finish.

Closes #5297

Change

  • Server (ClaudeAdapterV2): a top-level synthetic Stop hook feedback: frame that isn't the active /goal check becomes a completed notification item in the current run, with the hook's command and reason as its detail. /goal handling is unchanged.
  • Web and mobile folds: an assistant message followed by that notification in the same run stays outside the "Worked for" fold. The notification stays visible, as notifications already do, and tool and reasoning work still folds. On web, the notice doesn't start a new response, so the run isn't split.
  • Contracts: one shared constant for the notice summary, used by server, web and mobile.

Turns without a Stop hook render as on main. The answer's copy/fork/timestamp row is unchanged, so web and mobile look the same.

Scope and approval

Bug report: #5297, with repros from two users on recent nightlies.

This is narrower than #14698, which keeps all assistant text visible in every turn and is waiting on product approval. This PR changes only Stop-hook continuations, where the folded message is the actual answer. If #14698 lands, this still adds the hook notice, so the reply to the hook doesn't read as the answer.

Verification

  • vp test run on the 3 changed test files: 260 server, 90 web, 43 mobile tests pass.
  • Before the fix, the new cases fail on main: the 3 adapter rows, the web fold case and the mobile fold case. Each fails because the answer is folded or no notice is emitted.
  • The web and mobile cases cover two Stop hooks in one run: both answers stay visible.
  • vp run check:changed: format, lint, knip, typecheck (35 packages), tests and react compiler all pass.
  • Not done: before/after screenshots in a running client, and checks on desktop, iOS and Android. I can add captures from a Replay (dev) run if that's useful.

Implemented by gpt-6.1-sol (Codex CLI). Designed and reviewed by Claude Opus 5.5 (Claude Code).

Checks

26 successful · 12 skipped
All checks passed
8bcf140

Added 1 commit

  • fix: Claude Stop-hook continuations no longer hide the answer8bcf140
Synced from GitHub

Approvability

Verdict: Approved at 8bcf140

Macroscope's review found this PR approvable — Claude Stop-hook continuations now show a notification and keep preceding assistant answers visible on web and mobile. The change is limited to conversation events and presentation, without changing hook execution or access controls.

You can add or adjust custom eligibility rules. Learn more.

Synced from GitHub

Review in Change Stack →

No actionable comments were generated in the recent review.

External image Recent review info
External image Run configuration
External image Commits

Reviewing files that changed from the base of the PR and between 32ecd71726fee16f335484c1f95cbce126d461f6 and 8bcf140e039e30796632c38853e8dcfaaff39188.

External image Files selected for processing: 7
  • apps/mobile/src/lib/threadActivity.test.ts
  • apps/mobile/src/lib/threadActivity.ts
  • apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.test.ts
  • apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.ts
  • apps/web/src/components/chat/MessagesTimeline.logic.test.ts
  • apps/web/src/components/chat/MessagesTimeline.logic.ts
  • packages/contracts/src/orchestrationV2.ts

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 9 remain after this review.

Walkthrough

Claude stop-hook feedback is now emitted as a completed system notification. Mobile and web timeline folding preserve assistant messages associated with those notifications, while retaining the notification and subsequent response in the visible sequence.

Changes: Stop-hook continuation visibility

Layer / File(s)Summary
Stop-hook notification projection
packages/contracts/src/orchestrationV2.ts, apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.ts, apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.test.ts
A shared summary identifies Stop-hook continuation notifications. The Claude adapter emits a completed system notification for qualifying synthetic root messages, using the message UUID when available and generating one otherwise. Tests cover feedback with and without a UUID and with or without an active goal.
Mobile feed folding
apps/mobile/src/lib/threadActivity.ts, apps/mobile/src/lib/threadActivity.test.ts
Run folding excludes assistant messages associated with Stop-hook continuation notifications from hidden entries. The test checks that the notification rows and associated assistant messages remain visible.
Web timeline folding
apps/web/src/components/chat/MessagesTimeline.logic.ts, apps/web/src/components/chat/MessagesTimeline.logic.test.ts
Stop-hook notifications do not start response boundaries. Turn folding preserves the associated assistant answers. Tests check notification and answer ordering and assistant metadata on the terminal response.

Priority: External image Normal

Estimated code review effort: 3 (Moderate) | ~25 minutes

Change: Bug fix · Severity of issue fixed: Medium

Sequence Diagram(s)

mermaid
1sequenceDiagram
2 participant ClaudeAdapterV2
3 participant MessagesTimelineLogic
4 participant deriveTurnFolds
5 ClaudeAdapterV2->>MessagesTimelineLogic: Provide completed Stop-hook system notification
6 MessagesTimelineLogic->>deriveTurnFolds: Pass stopHookAnswerIds
7 deriveTurnFolds-->>MessagesTimelineLogic: Preserve Stop-hook-associated assistant answers

Possibly related issues

  • pingdotgg/t3code#17382: The change preserves assistant messages for Stop-hook continuations, but does not address the broader expansion and state-reset failures described in this issue.

Suggested reviewers: juliusmarminge

Merge Risk: External image Minimal · up to 8bcf1

No actionable merge-blocking risk was established.

Pre-merge checks: Passed 4
Check nameStatusExplanation
Title checkPassedThe title is concise, follows the repository's conventional format, and accurately describes the main fix: keeping Claude Stop-hook continuation answers visible.
Description checkPassedThe description includes complete Problem, Change, Scope and approval, and Verification sections. It provides reproduction steps, implementation details, linked issue context, test results, and known …
Linked Issues checkPassedScope: direct link #5297 (open, Claude Code only). Objectives grouped below. No requirement comes from a closed or contextual issue.

1. [#5297] The assistant answer before the Stop hook stays visible…
Out of Scope Changes checkPassedAll changed hunks connect to #5297 or to its supporting tests:
- ClaudeAdapterV2.ts: adds the Stop-hook notification projection. It skips frames that nextClaudeGoal recognizes, so /goal handling is un…
Full details: Description check

Explanation

The description includes complete Problem, Change, Scope and approval, and Verification sections. It provides reproduction steps, implementation details, linked issue context, test results, and known verification gaps. Screenshots and device-specific checks are not included, but the omission is clearly stated and the description is otherwise complete.

Full details: Linked Issues check

Explanation

Scope: direct link #5297 (open, Claude Code only). Objectives grouped below. No requirement comes from a closed or contextual issue.

  1. [#5297] The assistant answer before the Stop hook stays visible by default. Implemented in apps/web/src/components/chat/MessagesTimeline.logic.ts: deriveStopHookAnswerIds maps each run to its last assistant message. When a stop-hook notification follows in that run, that message is excluded from turn folding (deriveTurnFolds). Mobile does the same in apps/mobile/src/lib/threadActivity.ts (deriveThreadFeedRunFolds excludes these messages from hidden entries). Tests in the three changed test files cover this case (author reports they pass; not re-run here).

  2. [#5297] The UI clearly shows that a Stop hook ran. Implemented in apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.ts: a top-level synthetic user frame starting with "Stop hook feedback:\n" becomes a completed notification turn item with source kind system. Its title and summary are CLAUDE_STOP_HOOK_CONTINUATION_SUMMARY and its detail is the hook command and reason. The constant is defined once in packages/contracts/src/orchestrationV2.ts and used by server, web and mobile.

  3. [#5297] The answer after the Stop hook stays visible. The post-hook reply is the run's terminal assistant message. Existing terminal-message handling keeps it visible. The diff does not change that path.

  4. [#5297] Visibility does not depend on expanding the work log. The notification is a work entry. On web, it no longer starts a new response (timelineEntryStartsResponse excludes stop-hook entries), so the run is not split. The author states notifications already remain visible on main. The fold loop code that handles notification entries was not re-read here; this rests on the diff and the author's test description.

  5. [#5297] Chronological order. The notification item gets its ordinal from resolveItemOrdinal in the server adapter. The web and mobile tests, as described, check that the intervening message, notification and final answer keep their timeline positions.

Not covered: the issue's request for other harnesses (Codex, others) is excluded by the issue itself. Test results were taken from the author's report and the diff; they were not re-run.

Full details: Out of Scope Changes check

Explanation

All changed hunks connect to #5297 or to its supporting tests:

  • ClaudeAdapterV2.ts: adds the Stop-hook notification projection. It skips frames that nextClaudeGoal recognizes, so /goal handling is unchanged. Its tests assert that goal flows emit no notification item.
  • MessagesTimeline.logic.ts: adds stop-hook answer detection, excludes stop-hook entries from response-start logic, and passes stopHookAnswerIds to both deriveTurnFolds call sites. All of these serve the fold and visibility requirement.
  • threadActivity.ts (mobile): applies the same fold exclusion on mobile.
  • packages/contracts/src/orchestrationV2.ts: one shared summary constant. This keeps server, web and mobile matching on the same string, which the notification detection depends on.
  • The three test files cover the new behavior.

No unrelated refactor, dependency change, or unrelated behavior change appears in the reviewed diff. The PR does not change non-Claude providers or turns without a Stop hook, as the description states.

Finishing Touches
🧪 Generate unit tests (beta)
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Comment @coderabbitai help to get the list of available commands.

Synced from GitHub

closing at @StiensWout's request under Provide evidence for the changed behavior.

Changes visible web and mobile conversation folds so the answer before a Claude Stop-hook continuation stays visible and a hook notice appears, but explicitly has no before/after running-client screenshots.

submission evidence: "Not done: before/after screenshots in a running client"

Attach before/after screenshots of a Stop-hook continuation after settlement, with the original answer and hook notice visible.

thanks for trying to help improve t3 code. if you think this closure was a mistake, please open a fresh pr addressing the requirement above, and link back to this one.