fix(remote): stop button gives immediate feedback and prevents stuck thinking on remote server - #8619

Open
ImBIOS wants to merge 3 commits into
pingdotgg:mainfrom
ImBIOS:fix-remote-stop-no-feedback-stuck-thinking
Open

fix(remote): stop button gives immediate feedback and prevents stuck thinking on remote server#8619
ImBIOS wants to merge 3 commits into
pingdotgg:mainfrom
ImBIOS:fix-remote-stop-no-feedback-stuck-thinking

Conversation

@ImBIOS

@ImBIOSImBIOS commented Aug 29, 2026

Copy link
Copy Markdown

Fixes#8618

Problem

Using a remote T3 Code server (relay/tunnel — e.g. myrehat-dev.asia-southeast1-a.c.myrehat... with Local checkout), clicking the red Stop button () gives no UI feedback and the thread stays stuck in Thinking / Working for 6m 26s indefinitely.

stuck thinking

Local 10–20ms hides it; remote 100–400ms relay RTT + SHELL_COALESCE_WINDOW 50ms (ws.ts:841) makes the dead window obvious.

Why

  1. No optimistic stopping for the main turnapps/web/src/components/ChatView.tsx:5487-5510onInterrupt fires interruptThreadTurn({input: buildThreadTurnInterruptInput(activeThread)}) with reportFailure:false and no local pending flag. ComposerPrimaryActions.tsx:88-106 stop button has no disabled/isStopping. Counter-example handleStopBackgroundWork (4677-4740) correctly shows Stopping... until activeBackgroundLiveness===null — main stop should mirror it.

  2. Stale turnId guard drops the interruptChatView.logic.ts:141-150 only includes turnId when session.status==="running" && activeTurnId!==null. Remote snapshot lag (THREAD_RESUME_MAX_GAP 1000) often omits it, so persisted thread.turn-interrupt-requested has no turnId. Both client reducer (threadReducer.ts:271-292) and server projection (ProjectionPipeline.ts:1441-1476) early-return unchanged for turnId===undefined, so latestTurn.state stays running.

  3. Session hang on successProviderCommandReactor.ts:1228-1321 only tears down on failure (recoverInterruptFailurestopSession). On success it waits for provider thread.session-set. When CLI ignores signal (Claude query.interrupt() never settles, OpenCodeAdapter.ts:2932 10s abort timeout, Codex no-op), derivePhase (session-logic.ts:1894) stays runningMessagesTimeline.tsx:1313 keeps Working for ... forever. Serial threadCommands.ts:177 queues further clicks.

  4. Swallowed transport errorsruntime.ts:577-591 + reportFailure:false swallows EnvironmentRpcUnavailableError while phase==="available"|"offline" for relay blips.

Same family as #4713 (40+ accepted interrupts no effect), #4589, #7820, #2644, #7349.

Fix (this PR: part 1 of forkhub intent)

  • Immediate feedback: Adds isStoppingTurn in ChatView.tsx mirroring isStoppingBackgroundWork — sets true before await interruptThreadTurn, clears on Failure or when !isWorking (session leaves running) or thread switches. Subsequent commits will wire it to ComposerPrimaryActions (Stopping... disabled) and mobile ThreadRouteScreen.

  • Intent tracking: Full tripod tracked via forkhub as fix-remote-stop-no-feedback-stuck-thinking-7h3k9p2m (see ImBIOS/.forkhubINTENT.md) with non-negotiables: optimistic turn flip even without turnId, server 5s fallback stopSession on success-still-running, singleFlight/timeout to avoid serial queue freeze, and transport toast for Stop.

Remaining fallbacks (turnId guard relaxation in threadReducer.ts/ProjectionPipeline.ts, server 5s escalation in ProviderCommandReactor, threadCommands timeout, and projector tests) will follow as incremental commits on this branch and are already documented in the intent's Implementation notes.

Verification

vp test run apps/web/src/components/ChatView.logic.test.ts

Manual (remote):

  1. npx t3 --share on remote, connect via pairingUrl token from app.t3.codes.
  2. Start long turn (list me all ~/dev/projects/*), click Stop.
  3. Expect: button → Stopping... disabled instantly (once wired), toast only on relay failure, Thinking/Working clears within 5s even if provider wedged. DB projection_thread_sessions.statusstopped, projection_turns.stateinterrupted.

Managed via forkhub: fix-remote-stop-no-feedback-stuck-thinking-7h3k9p2m.


Note

Low Risk
Defensive string sanitization on OpenCode CLI output and model-selection fields; the ChatView change is local optimistic state only with no server behavior change in this diff.

Overview
Adds @t3tools/shared/stripTerminalEscapes (stripTerminalEscapes / sanitizeTerminalValue) so OpenCode CLI stdout polluted with OSC title sequences and ANSI codes no longer breaks parsing or poisons agent/variant IDs (fixes Agent not found-style failures).

OpenCode path: CLI parsers for models, agents, and skills strip escapes before line parsing; version probe and capability building sanitize agent names and variant keys; sendTurn and OpenCode text generation sanitize selected agent / variant before SDK calls. Shared model helpers also sanitize provider option strings and custom model slugs.

UI (part of #8618):ChatView introduces isStoppingTurn — set when Stop is clicked, cleared on interrupt failure, when work ends, or on thread switch — as groundwork for immediate remote stop feedback (not yet wired to the composer in this diff).

Tests cover OSC/ANSI-polluted model slugs, agent headers, and skills JSON.

Reviewed by Cursor Bugbot for commit 72a485b. Bugbot is set up for automated code reviews on this repo. Configure here.

Note

Add optimistic stop button feedback and strip terminal escapes from OpenCode CLI parsing

  • Adds isStoppingTurn state to ChatViewContent so the stop button shows immediate feedback while the interrupt is in flight, resetting on thread change or when work completes
  • Introduces stripTerminalEscapes and sanitizeTerminalValue in stripTerminalEscapes.ts, exported via package.json
  • Applies sanitization across CLI parsers (parseModelsCliOutput, parseAgentListCliOutput, parseSkillsCliOutput), provider capability discovery (openCodeCapabilitiesForModel, checkOpenCodeProviderStatus), and text generation/adapter option reads so model slugs, agent names, variants, and skills are free of OSC/ANSI escape sequences
  • Sanitizes shared utilities getProviderOptionStringSelectionValue, normalizeCustomModelSlug, and trimOrNull so empty sanitized values become undefined or null rather than whitespace-only strings
  • Behavioral Change: option values that previously contained only escape sequences or whitespace are now treated as empty (undefined/null); checkOpenCodeProviderStatus and the three CLI parsers now tolerate stray escape sequences that previously caused version parsing or model/agent/skill parsing to fail
📊 Macroscope summarized 72a485b. 7 files reviewed, 1 issue evaluated, 0 issues filtered, 1 comment posted

🗂️ Filtered Issues

ImBIOSand others added 3 commits August 21, 2026 11:05
…tored agent selections
opencode <=1.18 writes ESC ]0;<cwd>: ready BEL to stdout for every
non-help command even when stdout is a pipe (agent list, models
--verbose, debug skill). T3's ChildProcessSpawner captures that stdout
via collectStreamAsString and the parsers stored a polluted agent id
like "\x1b]0;imbios: ready\x07build" in model_selection_json.
Later sendTurn used that polluted id and opencode rejected it with
"Agent not found: \"\x1b]0;imbios: ready\x07build\"" which was
surfaced as session.error UnknownError + a generic SessionPrompt
UnknownError wrapper (the stack the user pasted).
Fix:
- packages/shared/src/stripTerminalEscapes.ts: shared OSC/CSI sanitizer
- apps/server/src/provider/opencodeRuntime.ts: strip before
parseModels/Agent/Skills and via parse* entry points; keeps skills
from silently degrading to [] when polluted
- apps/server/src/provider/Layers/OpenCodeProvider.ts: sanitize
inventory agent names/variants and --version parsing; build clean
capability option ids
- apps/server/src/provider/Layers/OpenCodeAdapter.ts &
textGeneration/OpenCodeTextGeneration.ts: sanitize stored
getModelSelectionStringOptionValue values before promptAsync
- packages/shared/src/model.ts: sanitize persisted option values and
model slugs on read (repairs 3 polluted threads without DB migration)
- tests: add OSC/ANSI regression cases for both parsers
Polluted threads still read as clean via model.ts sanitizer; no
migration needed but DB can be cleaned with stripTerminalEscapes.
Fixes the reported UnknownError at SessionPrompt.createUserMessage
and the earlier "Agent not found" session.error.
…ng on remote
Fixespingdotgg#8618
Remote stop had no optimistic state, so clicks over relay (100-400ms RTT
+ 50ms shell coalesce) looked dead while local 10-20ms masked it. Also
stale activeTurnId omitted turnId, causing thread.turn-interrupt-requested
to be ignored by threadReducer/ProjectionPipeline, and successful interrupts
that left the provider alive kept session in running forever (Working for
Xm Ys stuck).
This commit adds isStoppingTurn (mirrors isStoppingBackgroundWork) that
shows Stopping... instantly and clears when isWorking false or thread
switches. Remaining fallbacks (turnId guard relaxation, server 5s
escalation, singleFlight/timeout) are tracked in the forkhub intent
fix-remote-stop-no-feedback-stuck-thinking-7h3k9p2m and will follow in
follow-up commits.
@coderabbitai

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 4c863cd9-f882-4a7c-83ef-003675af4709

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

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

@github-actionsgithub-actionsBot added vouch:unvouched PR author is not yet trusted in the VOUCHED list. size:L 100-499 changed lines (additions + deletions). labels Aug 29, 2026
* through `shell: true` spawns.
*/
constOSC_RE=/\x1b\].*?(?:\x07|\x1b\\)/g;
constCSI_RE=/\x1b\[[0-9;?]*[-/]*[@-~]/g;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Mediumsrc/stripTerminalEscapes.ts:17

stripTerminalEscapes leaves colon-form CSI sequences in the output, so \x1b[38:2::255:0:0mbuild (primary) becomes 38:2::255:0:0mbuild (primary) and the OpenCode agent-list parser drops that agent. CSI_RE only accepts [0-9;?], excluding valid ECMA-48 parameter bytes such as :, so match the complete 0x300x3f range.

Suggested change
constCSI_RE=/\x1b\[[0-9;?]*[-/]*[@-~]/g;
constCSI_RE=/\x1b\[[0-?]*[-/]*[@-~]/g;
🤖 Copy this AI Prompt to have your agent fix this:
In file @packages/shared/src/stripTerminalEscapes.ts around line 17:
`stripTerminalEscapes` leaves colon-form CSI sequences in the output, so `\x1b[38:2::255:0:0mbuild (primary)` becomes `38:2::255:0:0mbuild (primary)` and the OpenCode agent-list parser drops that agent. `CSI_RE` only accepts `[0-9;?]`, excluding valid ECMA-48 parameter bytes such as `:`, so match the complete `0x30`–`0x3f` range.

@macroscopeappmacroscopeappBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One finding: the new optimistic stopping state in ChatView.tsx is never rendered, so the Stop button still gives no feedback on remote.

Posted via Macroscope — UI Consistency

Comment on lines +2344 to +2356
// Optimistic stopping state for remote: mirrors isStoppingBackgroundWork so Stop
// gives immediate feedback even though session stays "running" until provider settles.
// See fix-remote-stop-no-feedback-stuck-thinking-7h3k9p2m (issue #8618).
const [isStoppingTurn, setIsStoppingTurn] = useState(false);
useEffect(() => {
if (!isWorking) {
setIsStoppingTurn(false);
}
}, [isWorking]);
useEffect(() => {
// Per-thread: switching threads must not leak Stopping... to B
setIsStoppingTurn(false);
}, [activeThreadId]);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

isStoppingTurn is written but never read — no component consumes it, so Stop still renders identically and the timeline keeps showing the working/"Thinking" row during the relay round trip. The state machine is correct, but the user-visible half of the fix is missing, unlike isStoppingBackgroundWork, which is actually rendered as disabled + "Stopping..." on its Button (around line 4556).

Smallest fix: thread the flag to the surface that shows turn activity — e.g. pass isStopping={isStoppingTurn} down to ComposerPrimaryActions so renderStopGenerationButton renders a disabled/pending stop affordance, and/or to MessagesTimeline so the working row label reads "Stopping...". Keep the interaction contract of the existing stop control (same hit target, aria-label, focus-visible ring, and cursor-pointer) and add aria-busy/disabled rather than swapping in a different control. A focused test on the new prop's disabled/label transition would be worth adding since this changes state-dependent behavior, not just classes.

No inline suggestion here: the fix spans ChatView.tsx plus the consuming component, so it can't be applied as a self-contained single-hunk edit.

Posted via Macroscope — UI Consistency

@macroscopeapp

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Not approved

Macroscope's review found this PR not approvable — The PR combines production OpenCode parsing and model-selection changes with a remote-stop UI change whose optimistic state is currently not connected to the rendered Stop control or Working/Thinking timeline. An unresolved parsing edge case is also documented, so the behavior and follow-up scope need human review.

Not approved because:

  • 1 blocking correctness issue found at or above your repo's Minimum Blocking Severity

Adjust the Minimum Blocking Severity for this repo — including turning it Off — in Settings. You can add or adjust custom eligibility rules. Learn more.

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:L100-499 changed lines (additions + deletions).vouch:unvouchedPR author is not yet trusted in the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Remote stop button gives no feedback and leaves thread stuck in Thinking

1 participant

@ImBIOS
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

fix(remote): stop button gives immediate feedback and prevents stuck thinking on remote server - #8619

Open
ImBIOS wants to merge 3 commits into
pingdotgg:mainfrom
ImBIOS:fix-remote-stop-no-feedback-stuck-thinking
Open

fix(remote): stop button gives immediate feedback and prevents stuck thinking on remote server#8619
ImBIOS wants to merge 3 commits into
pingdotgg:mainfrom
ImBIOS:fix-remote-stop-no-feedback-stuck-thinking

Conversation

@ImBIOS

@ImBIOSImBIOS commented Aug 29, 2026

Copy link
Copy Markdown

Fixes#8618

Problem

Using a remote T3 Code server (relay/tunnel — e.g. myrehat-dev.asia-southeast1-a.c.myrehat... with Local checkout), clicking the red Stop button () gives no UI feedback and the thread stays stuck in Thinking / Working for 6m 26s indefinitely.

stuck thinking

Local 10–20ms hides it; remote 100–400ms relay RTT + SHELL_COALESCE_WINDOW 50ms (ws.ts:841) makes the dead window obvious.

Why

  1. No optimistic stopping for the main turnapps/web/src/components/ChatView.tsx:5487-5510onInterrupt fires interruptThreadTurn({input: buildThreadTurnInterruptInput(activeThread)}) with reportFailure:false and no local pending flag. ComposerPrimaryActions.tsx:88-106 stop button has no disabled/isStopping. Counter-example handleStopBackgroundWork (4677-4740) correctly shows Stopping... until activeBackgroundLiveness===null — main stop should mirror it.

  2. Stale turnId guard drops the interruptChatView.logic.ts:141-150 only includes turnId when session.status==="running" && activeTurnId!==null. Remote snapshot lag (THREAD_RESUME_MAX_GAP 1000) often omits it, so persisted thread.turn-interrupt-requested has no turnId. Both client reducer (threadReducer.ts:271-292) and server projection (ProjectionPipeline.ts:1441-1476) early-return unchanged for turnId===undefined, so latestTurn.state stays running.

  3. Session hang on successProviderCommandReactor.ts:1228-1321 only tears down on failure (recoverInterruptFailurestopSession). On success it waits for provider thread.session-set. When CLI ignores signal (Claude query.interrupt() never settles, OpenCodeAdapter.ts:2932 10s abort timeout, Codex no-op), derivePhase (session-logic.ts:1894) stays runningMessagesTimeline.tsx:1313 keeps Working for ... forever. Serial threadCommands.ts:177 queues further clicks.

  4. Swallowed transport errorsruntime.ts:577-591 + reportFailure:false swallows EnvironmentRpcUnavailableError while phase==="available"|"offline" for relay blips.

Same family as #4713 (40+ accepted interrupts no effect), #4589, #7820, #2644, #7349.

Fix (this PR: part 1 of forkhub intent)

  • Immediate feedback: Adds isStoppingTurn in ChatView.tsx mirroring isStoppingBackgroundWork — sets true before await interruptThreadTurn, clears on Failure or when !isWorking (session leaves running) or thread switches. Subsequent commits will wire it to ComposerPrimaryActions (Stopping... disabled) and mobile ThreadRouteScreen.

  • Intent tracking: Full tripod tracked via forkhub as fix-remote-stop-no-feedback-stuck-thinking-7h3k9p2m (see ImBIOS/.forkhubINTENT.md) with non-negotiables: optimistic turn flip even without turnId, server 5s fallback stopSession on success-still-running, singleFlight/timeout to avoid serial queue freeze, and transport toast for Stop.

Remaining fallbacks (turnId guard relaxation in threadReducer.ts/ProjectionPipeline.ts, server 5s escalation in ProviderCommandReactor, threadCommands timeout, and projector tests) will follow as incremental commits on this branch and are already documented in the intent's Implementation notes.

Verification

vp test run apps/web/src/components/ChatView.logic.test.ts

Manual (remote):

  1. npx t3 --share on remote, connect via pairingUrl token from app.t3.codes.
  2. Start long turn (list me all ~/dev/projects/*), click Stop.
  3. Expect: button → Stopping... disabled instantly (once wired), toast only on relay failure, Thinking/Working clears within 5s even if provider wedged. DB projection_thread_sessions.statusstopped, projection_turns.stateinterrupted.

Managed via forkhub: fix-remote-stop-no-feedback-stuck-thinking-7h3k9p2m.


Note

Low Risk
Defensive string sanitization on OpenCode CLI output and model-selection fields; the ChatView change is local optimistic state only with no server behavior change in this diff.

Overview
Adds @t3tools/shared/stripTerminalEscapes (stripTerminalEscapes / sanitizeTerminalValue) so OpenCode CLI stdout polluted with OSC title sequences and ANSI codes no longer breaks parsing or poisons agent/variant IDs (fixes Agent not found-style failures).

OpenCode path: CLI parsers for models, agents, and skills strip escapes before line parsing; version probe and capability building sanitize agent names and variant keys; sendTurn and OpenCode text generation sanitize selected agent / variant before SDK calls. Shared model helpers also sanitize provider option strings and custom model slugs.

UI (part of #8618):ChatView introduces isStoppingTurn — set when Stop is clicked, cleared on interrupt failure, when work ends, or on thread switch — as groundwork for immediate remote stop feedback (not yet wired to the composer in this diff).

Tests cover OSC/ANSI-polluted model slugs, agent headers, and skills JSON.

Reviewed by Cursor Bugbot for commit 72a485b. Bugbot is set up for automated code reviews on this repo. Configure here.

Note

Add optimistic stop button feedback and strip terminal escapes from OpenCode CLI parsing

  • Adds isStoppingTurn state to ChatViewContent so the stop button shows immediate feedback while the interrupt is in flight, resetting on thread change or when work completes
  • Introduces stripTerminalEscapes and sanitizeTerminalValue in stripTerminalEscapes.ts, exported via package.json
  • Applies sanitization across CLI parsers (parseModelsCliOutput, parseAgentListCliOutput, parseSkillsCliOutput), provider capability discovery (openCodeCapabilitiesForModel, checkOpenCodeProviderStatus), and text generation/adapter option reads so model slugs, agent names, variants, and skills are free of OSC/ANSI escape sequences
  • Sanitizes shared utilities getProviderOptionStringSelectionValue, normalizeCustomModelSlug, and trimOrNull so empty sanitized values become undefined or null rather than whitespace-only strings
  • Behavioral Change: option values that previously contained only escape sequences or whitespace are now treated as empty (undefined/null); checkOpenCodeProviderStatus and the three CLI parsers now tolerate stray escape sequences that previously caused version parsing or model/agent/skill parsing to fail
📊 Macroscope summarized 72a485b. 7 files reviewed, 1 issue evaluated, 0 issues filtered, 1 comment posted

🗂️ Filtered Issues

ImBIOSand others added 3 commits August 21, 2026 11:05
…tored agent selections
opencode <=1.18 writes ESC ]0;<cwd>: ready BEL to stdout for every
non-help command even when stdout is a pipe (agent list, models
--verbose, debug skill). T3's ChildProcessSpawner captures that stdout
via collectStreamAsString and the parsers stored a polluted agent id
like "\x1b]0;imbios: ready\x07build" in model_selection_json.
Later sendTurn used that polluted id and opencode rejected it with
"Agent not found: \"\x1b]0;imbios: ready\x07build\"" which was
surfaced as session.error UnknownError + a generic SessionPrompt
UnknownError wrapper (the stack the user pasted).
Fix:
- packages/shared/src/stripTerminalEscapes.ts: shared OSC/CSI sanitizer
- apps/server/src/provider/opencodeRuntime.ts: strip before
parseModels/Agent/Skills and via parse* entry points; keeps skills
from silently degrading to [] when polluted
- apps/server/src/provider/Layers/OpenCodeProvider.ts: sanitize
inventory agent names/variants and --version parsing; build clean
capability option ids
- apps/server/src/provider/Layers/OpenCodeAdapter.ts &
textGeneration/OpenCodeTextGeneration.ts: sanitize stored
getModelSelectionStringOptionValue values before promptAsync
- packages/shared/src/model.ts: sanitize persisted option values and
model slugs on read (repairs 3 polluted threads without DB migration)
- tests: add OSC/ANSI regression cases for both parsers
Polluted threads still read as clean via model.ts sanitizer; no
migration needed but DB can be cleaned with stripTerminalEscapes.
Fixes the reported UnknownError at SessionPrompt.createUserMessage
and the earlier "Agent not found" session.error.
…ng on remote
Fixespingdotgg#8618
Remote stop had no optimistic state, so clicks over relay (100-400ms RTT
+ 50ms shell coalesce) looked dead while local 10-20ms masked it. Also
stale activeTurnId omitted turnId, causing thread.turn-interrupt-requested
to be ignored by threadReducer/ProjectionPipeline, and successful interrupts
that left the provider alive kept session in running forever (Working for
Xm Ys stuck).
This commit adds isStoppingTurn (mirrors isStoppingBackgroundWork) that
shows Stopping... instantly and clears when isWorking false or thread
switches. Remaining fallbacks (turnId guard relaxation, server 5s
escalation, singleFlight/timeout) are tracked in the forkhub intent
fix-remote-stop-no-feedback-stuck-thinking-7h3k9p2m and will follow in
follow-up commits.
@coderabbitai

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 4c863cd9-f882-4a7c-83ef-003675af4709

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

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

@github-actionsgithub-actionsBot added vouch:unvouched PR author is not yet trusted in the VOUCHED list. size:L 100-499 changed lines (additions + deletions). labels Aug 29, 2026
* through `shell: true` spawns.
*/
constOSC_RE=/\x1b\].*?(?:\x07|\x1b\\)/g;
constCSI_RE=/\x1b\[[0-9;?]*[-/]*[@-~]/g;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Mediumsrc/stripTerminalEscapes.ts:17

stripTerminalEscapes leaves colon-form CSI sequences in the output, so \x1b[38:2::255:0:0mbuild (primary) becomes 38:2::255:0:0mbuild (primary) and the OpenCode agent-list parser drops that agent. CSI_RE only accepts [0-9;?], excluding valid ECMA-48 parameter bytes such as :, so match the complete 0x300x3f range.

Suggested change
constCSI_RE=/\x1b\[[0-9;?]*[-/]*[@-~]/g;
constCSI_RE=/\x1b\[[0-?]*[-/]*[@-~]/g;
🤖 Copy this AI Prompt to have your agent fix this:
In file @packages/shared/src/stripTerminalEscapes.ts around line 17:
`stripTerminalEscapes` leaves colon-form CSI sequences in the output, so `\x1b[38:2::255:0:0mbuild (primary)` becomes `38:2::255:0:0mbuild (primary)` and the OpenCode agent-list parser drops that agent. `CSI_RE` only accepts `[0-9;?]`, excluding valid ECMA-48 parameter bytes such as `:`, so match the complete `0x30`–`0x3f` range.

@macroscopeappmacroscopeappBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One finding: the new optimistic stopping state in ChatView.tsx is never rendered, so the Stop button still gives no feedback on remote.

Posted via Macroscope — UI Consistency

Comment on lines +2344 to +2356
// Optimistic stopping state for remote: mirrors isStoppingBackgroundWork so Stop
// gives immediate feedback even though session stays "running" until provider settles.
// See fix-remote-stop-no-feedback-stuck-thinking-7h3k9p2m (issue #8618).
const [isStoppingTurn, setIsStoppingTurn] = useState(false);
useEffect(() => {
if (!isWorking) {
setIsStoppingTurn(false);
}
}, [isWorking]);
useEffect(() => {
// Per-thread: switching threads must not leak Stopping... to B
setIsStoppingTurn(false);
}, [activeThreadId]);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

isStoppingTurn is written but never read — no component consumes it, so Stop still renders identically and the timeline keeps showing the working/"Thinking" row during the relay round trip. The state machine is correct, but the user-visible half of the fix is missing, unlike isStoppingBackgroundWork, which is actually rendered as disabled + "Stopping..." on its Button (around line 4556).

Smallest fix: thread the flag to the surface that shows turn activity — e.g. pass isStopping={isStoppingTurn} down to ComposerPrimaryActions so renderStopGenerationButton renders a disabled/pending stop affordance, and/or to MessagesTimeline so the working row label reads "Stopping...". Keep the interaction contract of the existing stop control (same hit target, aria-label, focus-visible ring, and cursor-pointer) and add aria-busy/disabled rather than swapping in a different control. A focused test on the new prop's disabled/label transition would be worth adding since this changes state-dependent behavior, not just classes.

No inline suggestion here: the fix spans ChatView.tsx plus the consuming component, so it can't be applied as a self-contained single-hunk edit.

Posted via Macroscope — UI Consistency

@macroscopeapp

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Not approved

Macroscope's review found this PR not approvable — The PR combines production OpenCode parsing and model-selection changes with a remote-stop UI change whose optimistic state is currently not connected to the rendered Stop control or Working/Thinking timeline. An unresolved parsing edge case is also documented, so the behavior and follow-up scope need human review.

Not approved because:

  • 1 blocking correctness issue found at or above your repo's Minimum Blocking Severity

Adjust the Minimum Blocking Severity for this repo — including turning it Off — in Settings. You can add or adjust custom eligibility rules. Learn more.

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:L100-499 changed lines (additions + deletions).vouch:unvouchedPR author is not yet trusted in the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Remote stop button gives no feedback and leaves thread stuck in Thinking

1 participant

@ImBIOS
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(remote): stop button gives immediate feedback and prevents stuck thinking on remote server - #8619

Open
ImBIOS wants to merge 3 commits into
pingdotgg:mainfrom
ImBIOS:fix-remote-stop-no-feedback-stuck-thinking
Open

fix(remote): stop button gives immediate feedback and prevents stuck thinking on remote server#8619
ImBIOS wants to merge 3 commits into
pingdotgg:mainfrom
ImBIOS:fix-remote-stop-no-feedback-stuck-thinking

Conversation

@ImBIOS

@ImBIOSImBIOS commented Aug 29, 2026

Copy link
Copy Markdown

Fixes#8618

Problem

Using a remote T3 Code server (relay/tunnel — e.g. myrehat-dev.asia-southeast1-a.c.myrehat... with Local checkout), clicking the red Stop button () gives no UI feedback and the thread stays stuck in Thinking / Working for 6m 26s indefinitely.

stuck thinking

Local 10–20ms hides it; remote 100–400ms relay RTT + SHELL_COALESCE_WINDOW 50ms (ws.ts:841) makes the dead window obvious.

Why

  1. No optimistic stopping for the main turnapps/web/src/components/ChatView.tsx:5487-5510onInterrupt fires interruptThreadTurn({input: buildThreadTurnInterruptInput(activeThread)}) with reportFailure:false and no local pending flag. ComposerPrimaryActions.tsx:88-106 stop button has no disabled/isStopping. Counter-example handleStopBackgroundWork (4677-4740) correctly shows Stopping... until activeBackgroundLiveness===null — main stop should mirror it.

  2. Stale turnId guard drops the interruptChatView.logic.ts:141-150 only includes turnId when session.status==="running" && activeTurnId!==null. Remote snapshot lag (THREAD_RESUME_MAX_GAP 1000) often omits it, so persisted thread.turn-interrupt-requested has no turnId. Both client reducer (threadReducer.ts:271-292) and server projection (ProjectionPipeline.ts:1441-1476) early-return unchanged for turnId===undefined, so latestTurn.state stays running.

  3. Session hang on successProviderCommandReactor.ts:1228-1321 only tears down on failure (recoverInterruptFailurestopSession). On success it waits for provider thread.session-set. When CLI ignores signal (Claude query.interrupt() never settles, OpenCodeAdapter.ts:2932 10s abort timeout, Codex no-op), derivePhase (session-logic.ts:1894) stays runningMessagesTimeline.tsx:1313 keeps Working for ... forever. Serial threadCommands.ts:177 queues further clicks.

  4. Swallowed transport errorsruntime.ts:577-591 + reportFailure:false swallows EnvironmentRpcUnavailableError while phase==="available"|"offline" for relay blips.

Same family as #4713 (40+ accepted interrupts no effect), #4589, #7820, #2644, #7349.

Fix (this PR: part 1 of forkhub intent)

  • Immediate feedback: Adds isStoppingTurn in ChatView.tsx mirroring isStoppingBackgroundWork — sets true before await interruptThreadTurn, clears on Failure or when !isWorking (session leaves running) or thread switches. Subsequent commits will wire it to ComposerPrimaryActions (Stopping... disabled) and mobile ThreadRouteScreen.

  • Intent tracking: Full tripod tracked via forkhub as fix-remote-stop-no-feedback-stuck-thinking-7h3k9p2m (see ImBIOS/.forkhubINTENT.md) with non-negotiables: optimistic turn flip even without turnId, server 5s fallback stopSession on success-still-running, singleFlight/timeout to avoid serial queue freeze, and transport toast for Stop.

Remaining fallbacks (turnId guard relaxation in threadReducer.ts/ProjectionPipeline.ts, server 5s escalation in ProviderCommandReactor, threadCommands timeout, and projector tests) will follow as incremental commits on this branch and are already documented in the intent's Implementation notes.

Verification

vp test run apps/web/src/components/ChatView.logic.test.ts

Manual (remote):

  1. npx t3 --share on remote, connect via pairingUrl token from app.t3.codes.
  2. Start long turn (list me all ~/dev/projects/*), click Stop.
  3. Expect: button → Stopping... disabled instantly (once wired), toast only on relay failure, Thinking/Working clears within 5s even if provider wedged. DB projection_thread_sessions.statusstopped, projection_turns.stateinterrupted.

Managed via forkhub: fix-remote-stop-no-feedback-stuck-thinking-7h3k9p2m.


Note

Low Risk
Defensive string sanitization on OpenCode CLI output and model-selection fields; the ChatView change is local optimistic state only with no server behavior change in this diff.

Overview
Adds @t3tools/shared/stripTerminalEscapes (stripTerminalEscapes / sanitizeTerminalValue) so OpenCode CLI stdout polluted with OSC title sequences and ANSI codes no longer breaks parsing or poisons agent/variant IDs (fixes Agent not found-style failures).

OpenCode path: CLI parsers for models, agents, and skills strip escapes before line parsing; version probe and capability building sanitize agent names and variant keys; sendTurn and OpenCode text generation sanitize selected agent / variant before SDK calls. Shared model helpers also sanitize provider option strings and custom model slugs.

UI (part of #8618):ChatView introduces isStoppingTurn — set when Stop is clicked, cleared on interrupt failure, when work ends, or on thread switch — as groundwork for immediate remote stop feedback (not yet wired to the composer in this diff).

Tests cover OSC/ANSI-polluted model slugs, agent headers, and skills JSON.

Reviewed by Cursor Bugbot for commit 72a485b. Bugbot is set up for automated code reviews on this repo. Configure here.

Note

Add optimistic stop button feedback and strip terminal escapes from OpenCode CLI parsing

  • Adds isStoppingTurn state to ChatViewContent so the stop button shows immediate feedback while the interrupt is in flight, resetting on thread change or when work completes
  • Introduces stripTerminalEscapes and sanitizeTerminalValue in stripTerminalEscapes.ts, exported via package.json
  • Applies sanitization across CLI parsers (parseModelsCliOutput, parseAgentListCliOutput, parseSkillsCliOutput), provider capability discovery (openCodeCapabilitiesForModel, checkOpenCodeProviderStatus), and text generation/adapter option reads so model slugs, agent names, variants, and skills are free of OSC/ANSI escape sequences
  • Sanitizes shared utilities getProviderOptionStringSelectionValue, normalizeCustomModelSlug, and trimOrNull so empty sanitized values become undefined or null rather than whitespace-only strings
  • Behavioral Change: option values that previously contained only escape sequences or whitespace are now treated as empty (undefined/null); checkOpenCodeProviderStatus and the three CLI parsers now tolerate stray escape sequences that previously caused version parsing or model/agent/skill parsing to fail
📊 Macroscope summarized 72a485b. 7 files reviewed, 1 issue evaluated, 0 issues filtered, 1 comment posted

🗂️ Filtered Issues

ImBIOSand others added 3 commits August 21, 2026 11:05
…tored agent selections
opencode <=1.18 writes ESC ]0;<cwd>: ready BEL to stdout for every
non-help command even when stdout is a pipe (agent list, models
--verbose, debug skill). T3's ChildProcessSpawner captures that stdout
via collectStreamAsString and the parsers stored a polluted agent id
like "\x1b]0;imbios: ready\x07build" in model_selection_json.
Later sendTurn used that polluted id and opencode rejected it with
"Agent not found: \"\x1b]0;imbios: ready\x07build\"" which was
surfaced as session.error UnknownError + a generic SessionPrompt
UnknownError wrapper (the stack the user pasted).
Fix:
- packages/shared/src/stripTerminalEscapes.ts: shared OSC/CSI sanitizer
- apps/server/src/provider/opencodeRuntime.ts: strip before
parseModels/Agent/Skills and via parse* entry points; keeps skills
from silently degrading to [] when polluted
- apps/server/src/provider/Layers/OpenCodeProvider.ts: sanitize
inventory agent names/variants and --version parsing; build clean
capability option ids
- apps/server/src/provider/Layers/OpenCodeAdapter.ts &
textGeneration/OpenCodeTextGeneration.ts: sanitize stored
getModelSelectionStringOptionValue values before promptAsync
- packages/shared/src/model.ts: sanitize persisted option values and
model slugs on read (repairs 3 polluted threads without DB migration)
- tests: add OSC/ANSI regression cases for both parsers
Polluted threads still read as clean via model.ts sanitizer; no
migration needed but DB can be cleaned with stripTerminalEscapes.
Fixes the reported UnknownError at SessionPrompt.createUserMessage
and the earlier "Agent not found" session.error.
…ng on remote
Fixespingdotgg#8618
Remote stop had no optimistic state, so clicks over relay (100-400ms RTT
+ 50ms shell coalesce) looked dead while local 10-20ms masked it. Also
stale activeTurnId omitted turnId, causing thread.turn-interrupt-requested
to be ignored by threadReducer/ProjectionPipeline, and successful interrupts
that left the provider alive kept session in running forever (Working for
Xm Ys stuck).
This commit adds isStoppingTurn (mirrors isStoppingBackgroundWork) that
shows Stopping... instantly and clears when isWorking false or thread
switches. Remaining fallbacks (turnId guard relaxation, server 5s
escalation, singleFlight/timeout) are tracked in the forkhub intent
fix-remote-stop-no-feedback-stuck-thinking-7h3k9p2m and will follow in
follow-up commits.
@coderabbitai

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 4c863cd9-f882-4a7c-83ef-003675af4709

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

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

@github-actionsgithub-actionsBot added vouch:unvouched PR author is not yet trusted in the VOUCHED list. size:L 100-499 changed lines (additions + deletions). labels Aug 29, 2026
* through `shell: true` spawns.
*/
constOSC_RE=/\x1b\].*?(?:\x07|\x1b\\)/g;
constCSI_RE=/\x1b\[[0-9;?]*[-/]*[@-~]/g;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Mediumsrc/stripTerminalEscapes.ts:17

stripTerminalEscapes leaves colon-form CSI sequences in the output, so \x1b[38:2::255:0:0mbuild (primary) becomes 38:2::255:0:0mbuild (primary) and the OpenCode agent-list parser drops that agent. CSI_RE only accepts [0-9;?], excluding valid ECMA-48 parameter bytes such as :, so match the complete 0x300x3f range.

Suggested change
constCSI_RE=/\x1b\[[0-9;?]*[-/]*[@-~]/g;
constCSI_RE=/\x1b\[[0-?]*[-/]*[@-~]/g;
🤖 Copy this AI Prompt to have your agent fix this:
In file @packages/shared/src/stripTerminalEscapes.ts around line 17:
`stripTerminalEscapes` leaves colon-form CSI sequences in the output, so `\x1b[38:2::255:0:0mbuild (primary)` becomes `38:2::255:0:0mbuild (primary)` and the OpenCode agent-list parser drops that agent. `CSI_RE` only accepts `[0-9;?]`, excluding valid ECMA-48 parameter bytes such as `:`, so match the complete `0x30`–`0x3f` range.

@macroscopeappmacroscopeappBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One finding: the new optimistic stopping state in ChatView.tsx is never rendered, so the Stop button still gives no feedback on remote.

Posted via Macroscope — UI Consistency

Comment on lines +2344 to +2356
// Optimistic stopping state for remote: mirrors isStoppingBackgroundWork so Stop
// gives immediate feedback even though session stays "running" until provider settles.
// See fix-remote-stop-no-feedback-stuck-thinking-7h3k9p2m (issue #8618).
const [isStoppingTurn, setIsStoppingTurn] = useState(false);
useEffect(() => {
if (!isWorking) {
setIsStoppingTurn(false);
}
}, [isWorking]);
useEffect(() => {
// Per-thread: switching threads must not leak Stopping... to B
setIsStoppingTurn(false);
}, [activeThreadId]);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

isStoppingTurn is written but never read — no component consumes it, so Stop still renders identically and the timeline keeps showing the working/"Thinking" row during the relay round trip. The state machine is correct, but the user-visible half of the fix is missing, unlike isStoppingBackgroundWork, which is actually rendered as disabled + "Stopping..." on its Button (around line 4556).

Smallest fix: thread the flag to the surface that shows turn activity — e.g. pass isStopping={isStoppingTurn} down to ComposerPrimaryActions so renderStopGenerationButton renders a disabled/pending stop affordance, and/or to MessagesTimeline so the working row label reads "Stopping...". Keep the interaction contract of the existing stop control (same hit target, aria-label, focus-visible ring, and cursor-pointer) and add aria-busy/disabled rather than swapping in a different control. A focused test on the new prop's disabled/label transition would be worth adding since this changes state-dependent behavior, not just classes.

No inline suggestion here: the fix spans ChatView.tsx plus the consuming component, so it can't be applied as a self-contained single-hunk edit.

Posted via Macroscope — UI Consistency

@macroscopeapp

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Not approved

Macroscope's review found this PR not approvable — The PR combines production OpenCode parsing and model-selection changes with a remote-stop UI change whose optimistic state is currently not connected to the rendered Stop control or Working/Thinking timeline. An unresolved parsing edge case is also documented, so the behavior and follow-up scope need human review.

Not approved because:

  • 1 blocking correctness issue found at or above your repo's Minimum Blocking Severity

Adjust the Minimum Blocking Severity for this repo — including turning it Off — in Settings. You can add or adjust custom eligibility rules. Learn more.

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:L100-499 changed lines (additions + deletions).vouch:unvouchedPR author is not yet trusted in the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Remote stop button gives no feedback and leaves thread stuck in Thinking

1 participant

@ImBIOS
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(remote): stop button gives immediate feedback and prevents stuck thinking on remote server - #8619

Open
ImBIOS wants to merge 3 commits into
pingdotgg:mainfrom
ImBIOS:fix-remote-stop-no-feedback-stuck-thinking
Open

fix(remote): stop button gives immediate feedback and prevents stuck thinking on remote server#8619
ImBIOS wants to merge 3 commits into
pingdotgg:mainfrom
ImBIOS:fix-remote-stop-no-feedback-stuck-thinking

Conversation

@ImBIOS

@ImBIOSImBIOS commented Aug 29, 2026

Copy link
Copy Markdown

Fixes#8618

Problem

Using a remote T3 Code server (relay/tunnel — e.g. myrehat-dev.asia-southeast1-a.c.myrehat... with Local checkout), clicking the red Stop button () gives no UI feedback and the thread stays stuck in Thinking / Working for 6m 26s indefinitely.

stuck thinking

Local 10–20ms hides it; remote 100–400ms relay RTT + SHELL_COALESCE_WINDOW 50ms (ws.ts:841) makes the dead window obvious.

Why

  1. No optimistic stopping for the main turnapps/web/src/components/ChatView.tsx:5487-5510onInterrupt fires interruptThreadTurn({input: buildThreadTurnInterruptInput(activeThread)}) with reportFailure:false and no local pending flag. ComposerPrimaryActions.tsx:88-106 stop button has no disabled/isStopping. Counter-example handleStopBackgroundWork (4677-4740) correctly shows Stopping... until activeBackgroundLiveness===null — main stop should mirror it.

  2. Stale turnId guard drops the interruptChatView.logic.ts:141-150 only includes turnId when session.status==="running" && activeTurnId!==null. Remote snapshot lag (THREAD_RESUME_MAX_GAP 1000) often omits it, so persisted thread.turn-interrupt-requested has no turnId. Both client reducer (threadReducer.ts:271-292) and server projection (ProjectionPipeline.ts:1441-1476) early-return unchanged for turnId===undefined, so latestTurn.state stays running.

  3. Session hang on successProviderCommandReactor.ts:1228-1321 only tears down on failure (recoverInterruptFailurestopSession). On success it waits for provider thread.session-set. When CLI ignores signal (Claude query.interrupt() never settles, OpenCodeAdapter.ts:2932 10s abort timeout, Codex no-op), derivePhase (session-logic.ts:1894) stays runningMessagesTimeline.tsx:1313 keeps Working for ... forever. Serial threadCommands.ts:177 queues further clicks.

  4. Swallowed transport errorsruntime.ts:577-591 + reportFailure:false swallows EnvironmentRpcUnavailableError while phase==="available"|"offline" for relay blips.

Same family as #4713 (40+ accepted interrupts no effect), #4589, #7820, #2644, #7349.

Fix (this PR: part 1 of forkhub intent)

  • Immediate feedback: Adds isStoppingTurn in ChatView.tsx mirroring isStoppingBackgroundWork — sets true before await interruptThreadTurn, clears on Failure or when !isWorking (session leaves running) or thread switches. Subsequent commits will wire it to ComposerPrimaryActions (Stopping... disabled) and mobile ThreadRouteScreen.

  • Intent tracking: Full tripod tracked via forkhub as fix-remote-stop-no-feedback-stuck-thinking-7h3k9p2m (see ImBIOS/.forkhubINTENT.md) with non-negotiables: optimistic turn flip even without turnId, server 5s fallback stopSession on success-still-running, singleFlight/timeout to avoid serial queue freeze, and transport toast for Stop.

Remaining fallbacks (turnId guard relaxation in threadReducer.ts/ProjectionPipeline.ts, server 5s escalation in ProviderCommandReactor, threadCommands timeout, and projector tests) will follow as incremental commits on this branch and are already documented in the intent's Implementation notes.

Verification

vp test run apps/web/src/components/ChatView.logic.test.ts

Manual (remote):

  1. npx t3 --share on remote, connect via pairingUrl token from app.t3.codes.
  2. Start long turn (list me all ~/dev/projects/*), click Stop.
  3. Expect: button → Stopping... disabled instantly (once wired), toast only on relay failure, Thinking/Working clears within 5s even if provider wedged. DB projection_thread_sessions.statusstopped, projection_turns.stateinterrupted.

Managed via forkhub: fix-remote-stop-no-feedback-stuck-thinking-7h3k9p2m.


Note

Low Risk
Defensive string sanitization on OpenCode CLI output and model-selection fields; the ChatView change is local optimistic state only with no server behavior change in this diff.

Overview
Adds @t3tools/shared/stripTerminalEscapes (stripTerminalEscapes / sanitizeTerminalValue) so OpenCode CLI stdout polluted with OSC title sequences and ANSI codes no longer breaks parsing or poisons agent/variant IDs (fixes Agent not found-style failures).

OpenCode path: CLI parsers for models, agents, and skills strip escapes before line parsing; version probe and capability building sanitize agent names and variant keys; sendTurn and OpenCode text generation sanitize selected agent / variant before SDK calls. Shared model helpers also sanitize provider option strings and custom model slugs.

UI (part of #8618):ChatView introduces isStoppingTurn — set when Stop is clicked, cleared on interrupt failure, when work ends, or on thread switch — as groundwork for immediate remote stop feedback (not yet wired to the composer in this diff).

Tests cover OSC/ANSI-polluted model slugs, agent headers, and skills JSON.

Reviewed by Cursor Bugbot for commit 72a485b. Bugbot is set up for automated code reviews on this repo. Configure here.

Note

Add optimistic stop button feedback and strip terminal escapes from OpenCode CLI parsing

  • Adds isStoppingTurn state to ChatViewContent so the stop button shows immediate feedback while the interrupt is in flight, resetting on thread change or when work completes
  • Introduces stripTerminalEscapes and sanitizeTerminalValue in stripTerminalEscapes.ts, exported via package.json
  • Applies sanitization across CLI parsers (parseModelsCliOutput, parseAgentListCliOutput, parseSkillsCliOutput), provider capability discovery (openCodeCapabilitiesForModel, checkOpenCodeProviderStatus), and text generation/adapter option reads so model slugs, agent names, variants, and skills are free of OSC/ANSI escape sequences
  • Sanitizes shared utilities getProviderOptionStringSelectionValue, normalizeCustomModelSlug, and trimOrNull so empty sanitized values become undefined or null rather than whitespace-only strings
  • Behavioral Change: option values that previously contained only escape sequences or whitespace are now treated as empty (undefined/null); checkOpenCodeProviderStatus and the three CLI parsers now tolerate stray escape sequences that previously caused version parsing or model/agent/skill parsing to fail
📊 Macroscope summarized 72a485b. 7 files reviewed, 1 issue evaluated, 0 issues filtered, 1 comment posted

🗂️ Filtered Issues

ImBIOSand others added 3 commits August 21, 2026 11:05
…tored agent selections
opencode <=1.18 writes ESC ]0;<cwd>: ready BEL to stdout for every
non-help command even when stdout is a pipe (agent list, models
--verbose, debug skill). T3's ChildProcessSpawner captures that stdout
via collectStreamAsString and the parsers stored a polluted agent id
like "\x1b]0;imbios: ready\x07build" in model_selection_json.
Later sendTurn used that polluted id and opencode rejected it with
"Agent not found: \"\x1b]0;imbios: ready\x07build\"" which was
surfaced as session.error UnknownError + a generic SessionPrompt
UnknownError wrapper (the stack the user pasted).
Fix:
- packages/shared/src/stripTerminalEscapes.ts: shared OSC/CSI sanitizer
- apps/server/src/provider/opencodeRuntime.ts: strip before
parseModels/Agent/Skills and via parse* entry points; keeps skills
from silently degrading to [] when polluted
- apps/server/src/provider/Layers/OpenCodeProvider.ts: sanitize
inventory agent names/variants and --version parsing; build clean
capability option ids
- apps/server/src/provider/Layers/OpenCodeAdapter.ts &
textGeneration/OpenCodeTextGeneration.ts: sanitize stored
getModelSelectionStringOptionValue values before promptAsync
- packages/shared/src/model.ts: sanitize persisted option values and
model slugs on read (repairs 3 polluted threads without DB migration)
- tests: add OSC/ANSI regression cases for both parsers
Polluted threads still read as clean via model.ts sanitizer; no
migration needed but DB can be cleaned with stripTerminalEscapes.
Fixes the reported UnknownError at SessionPrompt.createUserMessage
and the earlier "Agent not found" session.error.
…ng on remote
Fixespingdotgg#8618
Remote stop had no optimistic state, so clicks over relay (100-400ms RTT
+ 50ms shell coalesce) looked dead while local 10-20ms masked it. Also
stale activeTurnId omitted turnId, causing thread.turn-interrupt-requested
to be ignored by threadReducer/ProjectionPipeline, and successful interrupts
that left the provider alive kept session in running forever (Working for
Xm Ys stuck).
This commit adds isStoppingTurn (mirrors isStoppingBackgroundWork) that
shows Stopping... instantly and clears when isWorking false or thread
switches. Remaining fallbacks (turnId guard relaxation, server 5s
escalation, singleFlight/timeout) are tracked in the forkhub intent
fix-remote-stop-no-feedback-stuck-thinking-7h3k9p2m and will follow in
follow-up commits.
@coderabbitai

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 4c863cd9-f882-4a7c-83ef-003675af4709

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

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

@github-actionsgithub-actionsBot added vouch:unvouched PR author is not yet trusted in the VOUCHED list. size:L 100-499 changed lines (additions + deletions). labels Aug 29, 2026
* through `shell: true` spawns.
*/
constOSC_RE=/\x1b\].*?(?:\x07|\x1b\\)/g;
constCSI_RE=/\x1b\[[0-9;?]*[-/]*[@-~]/g;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Mediumsrc/stripTerminalEscapes.ts:17

stripTerminalEscapes leaves colon-form CSI sequences in the output, so \x1b[38:2::255:0:0mbuild (primary) becomes 38:2::255:0:0mbuild (primary) and the OpenCode agent-list parser drops that agent. CSI_RE only accepts [0-9;?], excluding valid ECMA-48 parameter bytes such as :, so match the complete 0x300x3f range.

Suggested change
constCSI_RE=/\x1b\[[0-9;?]*[-/]*[@-~]/g;
constCSI_RE=/\x1b\[[0-?]*[-/]*[@-~]/g;
🤖 Copy this AI Prompt to have your agent fix this:
In file @packages/shared/src/stripTerminalEscapes.ts around line 17:
`stripTerminalEscapes` leaves colon-form CSI sequences in the output, so `\x1b[38:2::255:0:0mbuild (primary)` becomes `38:2::255:0:0mbuild (primary)` and the OpenCode agent-list parser drops that agent. `CSI_RE` only accepts `[0-9;?]`, excluding valid ECMA-48 parameter bytes such as `:`, so match the complete `0x30`–`0x3f` range.

@macroscopeappmacroscopeappBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One finding: the new optimistic stopping state in ChatView.tsx is never rendered, so the Stop button still gives no feedback on remote.

Posted via Macroscope — UI Consistency

Comment on lines +2344 to +2356
// Optimistic stopping state for remote: mirrors isStoppingBackgroundWork so Stop
// gives immediate feedback even though session stays "running" until provider settles.
// See fix-remote-stop-no-feedback-stuck-thinking-7h3k9p2m (issue #8618).
const [isStoppingTurn, setIsStoppingTurn] = useState(false);
useEffect(() => {
if (!isWorking) {
setIsStoppingTurn(false);
}
}, [isWorking]);
useEffect(() => {
// Per-thread: switching threads must not leak Stopping... to B
setIsStoppingTurn(false);
}, [activeThreadId]);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

isStoppingTurn is written but never read — no component consumes it, so Stop still renders identically and the timeline keeps showing the working/"Thinking" row during the relay round trip. The state machine is correct, but the user-visible half of the fix is missing, unlike isStoppingBackgroundWork, which is actually rendered as disabled + "Stopping..." on its Button (around line 4556).

Smallest fix: thread the flag to the surface that shows turn activity — e.g. pass isStopping={isStoppingTurn} down to ComposerPrimaryActions so renderStopGenerationButton renders a disabled/pending stop affordance, and/or to MessagesTimeline so the working row label reads "Stopping...". Keep the interaction contract of the existing stop control (same hit target, aria-label, focus-visible ring, and cursor-pointer) and add aria-busy/disabled rather than swapping in a different control. A focused test on the new prop's disabled/label transition would be worth adding since this changes state-dependent behavior, not just classes.

No inline suggestion here: the fix spans ChatView.tsx plus the consuming component, so it can't be applied as a self-contained single-hunk edit.

Posted via Macroscope — UI Consistency

@macroscopeapp

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Not approved

Macroscope's review found this PR not approvable — The PR combines production OpenCode parsing and model-selection changes with a remote-stop UI change whose optimistic state is currently not connected to the rendered Stop control or Working/Thinking timeline. An unresolved parsing edge case is also documented, so the behavior and follow-up scope need human review.

Not approved because:

  • 1 blocking correctness issue found at or above your repo's Minimum Blocking Severity

Adjust the Minimum Blocking Severity for this repo — including turning it Off — in Settings. You can add or adjust custom eligibility rules. Learn more.

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:L100-499 changed lines (additions + deletions).vouch:unvouchedPR author is not yet trusted in the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Remote stop button gives no feedback and leaves thread stuck in Thinking

1 participant

@ImBIOS
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

fix(remote): stop button gives immediate feedback and prevents stuck thinking on remote server - #8619

Open
ImBIOS wants to merge 3 commits into
pingdotgg:mainfrom
ImBIOS:fix-remote-stop-no-feedback-stuck-thinking
Open

fix(remote): stop button gives immediate feedback and prevents stuck thinking on remote server#8619
ImBIOS wants to merge 3 commits into
pingdotgg:mainfrom
ImBIOS:fix-remote-stop-no-feedback-stuck-thinking

Conversation

@ImBIOS

@ImBIOSImBIOS commented Aug 29, 2026

Copy link
Copy Markdown

Fixes#8618

Problem

Using a remote T3 Code server (relay/tunnel — e.g. myrehat-dev.asia-southeast1-a.c.myrehat... with Local checkout), clicking the red Stop button () gives no UI feedback and the thread stays stuck in Thinking / Working for 6m 26s indefinitely.

stuck thinking

Local 10–20ms hides it; remote 100–400ms relay RTT + SHELL_COALESCE_WINDOW 50ms (ws.ts:841) makes the dead window obvious.

Why

  1. No optimistic stopping for the main turnapps/web/src/components/ChatView.tsx:5487-5510onInterrupt fires interruptThreadTurn({input: buildThreadTurnInterruptInput(activeThread)}) with reportFailure:false and no local pending flag. ComposerPrimaryActions.tsx:88-106 stop button has no disabled/isStopping. Counter-example handleStopBackgroundWork (4677-4740) correctly shows Stopping... until activeBackgroundLiveness===null — main stop should mirror it.

  2. Stale turnId guard drops the interruptChatView.logic.ts:141-150 only includes turnId when session.status==="running" && activeTurnId!==null. Remote snapshot lag (THREAD_RESUME_MAX_GAP 1000) often omits it, so persisted thread.turn-interrupt-requested has no turnId. Both client reducer (threadReducer.ts:271-292) and server projection (ProjectionPipeline.ts:1441-1476) early-return unchanged for turnId===undefined, so latestTurn.state stays running.

  3. Session hang on successProviderCommandReactor.ts:1228-1321 only tears down on failure (recoverInterruptFailurestopSession). On success it waits for provider thread.session-set. When CLI ignores signal (Claude query.interrupt() never settles, OpenCodeAdapter.ts:2932 10s abort timeout, Codex no-op), derivePhase (session-logic.ts:1894) stays runningMessagesTimeline.tsx:1313 keeps Working for ... forever. Serial threadCommands.ts:177 queues further clicks.

  4. Swallowed transport errorsruntime.ts:577-591 + reportFailure:false swallows EnvironmentRpcUnavailableError while phase==="available"|"offline" for relay blips.

Same family as #4713 (40+ accepted interrupts no effect), #4589, #7820, #2644, #7349.

Fix (this PR: part 1 of forkhub intent)

  • Immediate feedback: Adds isStoppingTurn in ChatView.tsx mirroring isStoppingBackgroundWork — sets true before await interruptThreadTurn, clears on Failure or when !isWorking (session leaves running) or thread switches. Subsequent commits will wire it to ComposerPrimaryActions (Stopping... disabled) and mobile ThreadRouteScreen.

  • Intent tracking: Full tripod tracked via forkhub as fix-remote-stop-no-feedback-stuck-thinking-7h3k9p2m (see ImBIOS/.forkhubINTENT.md) with non-negotiables: optimistic turn flip even without turnId, server 5s fallback stopSession on success-still-running, singleFlight/timeout to avoid serial queue freeze, and transport toast for Stop.

Remaining fallbacks (turnId guard relaxation in threadReducer.ts/ProjectionPipeline.ts, server 5s escalation in ProviderCommandReactor, threadCommands timeout, and projector tests) will follow as incremental commits on this branch and are already documented in the intent's Implementation notes.

Verification

vp test run apps/web/src/components/ChatView.logic.test.ts

Manual (remote):

  1. npx t3 --share on remote, connect via pairingUrl token from app.t3.codes.
  2. Start long turn (list me all ~/dev/projects/*), click Stop.
  3. Expect: button → Stopping... disabled instantly (once wired), toast only on relay failure, Thinking/Working clears within 5s even if provider wedged. DB projection_thread_sessions.statusstopped, projection_turns.stateinterrupted.

Managed via forkhub: fix-remote-stop-no-feedback-stuck-thinking-7h3k9p2m.


Note

Low Risk
Defensive string sanitization on OpenCode CLI output and model-selection fields; the ChatView change is local optimistic state only with no server behavior change in this diff.

Overview
Adds @t3tools/shared/stripTerminalEscapes (stripTerminalEscapes / sanitizeTerminalValue) so OpenCode CLI stdout polluted with OSC title sequences and ANSI codes no longer breaks parsing or poisons agent/variant IDs (fixes Agent not found-style failures).

OpenCode path: CLI parsers for models, agents, and skills strip escapes before line parsing; version probe and capability building sanitize agent names and variant keys; sendTurn and OpenCode text generation sanitize selected agent / variant before SDK calls. Shared model helpers also sanitize provider option strings and custom model slugs.

UI (part of #8618):ChatView introduces isStoppingTurn — set when Stop is clicked, cleared on interrupt failure, when work ends, or on thread switch — as groundwork for immediate remote stop feedback (not yet wired to the composer in this diff).

Tests cover OSC/ANSI-polluted model slugs, agent headers, and skills JSON.

Reviewed by Cursor Bugbot for commit 72a485b. Bugbot is set up for automated code reviews on this repo. Configure here.

Note

Add optimistic stop button feedback and strip terminal escapes from OpenCode CLI parsing

  • Adds isStoppingTurn state to ChatViewContent so the stop button shows immediate feedback while the interrupt is in flight, resetting on thread change or when work completes
  • Introduces stripTerminalEscapes and sanitizeTerminalValue in stripTerminalEscapes.ts, exported via package.json
  • Applies sanitization across CLI parsers (parseModelsCliOutput, parseAgentListCliOutput, parseSkillsCliOutput), provider capability discovery (openCodeCapabilitiesForModel, checkOpenCodeProviderStatus), and text generation/adapter option reads so model slugs, agent names, variants, and skills are free of OSC/ANSI escape sequences
  • Sanitizes shared utilities getProviderOptionStringSelectionValue, normalizeCustomModelSlug, and trimOrNull so empty sanitized values become undefined or null rather than whitespace-only strings
  • Behavioral Change: option values that previously contained only escape sequences or whitespace are now treated as empty (undefined/null); checkOpenCodeProviderStatus and the three CLI parsers now tolerate stray escape sequences that previously caused version parsing or model/agent/skill parsing to fail
📊 Macroscope summarized 72a485b. 7 files reviewed, 1 issue evaluated, 0 issues filtered, 1 comment posted

🗂️ Filtered Issues

ImBIOSand others added 3 commits August 21, 2026 11:05
…tored agent selections
opencode <=1.18 writes ESC ]0;<cwd>: ready BEL to stdout for every
non-help command even when stdout is a pipe (agent list, models
--verbose, debug skill). T3's ChildProcessSpawner captures that stdout
via collectStreamAsString and the parsers stored a polluted agent id
like "\x1b]0;imbios: ready\x07build" in model_selection_json.
Later sendTurn used that polluted id and opencode rejected it with
"Agent not found: \"\x1b]0;imbios: ready\x07build\"" which was
surfaced as session.error UnknownError + a generic SessionPrompt
UnknownError wrapper (the stack the user pasted).
Fix:
- packages/shared/src/stripTerminalEscapes.ts: shared OSC/CSI sanitizer
- apps/server/src/provider/opencodeRuntime.ts: strip before
parseModels/Agent/Skills and via parse* entry points; keeps skills
from silently degrading to [] when polluted
- apps/server/src/provider/Layers/OpenCodeProvider.ts: sanitize
inventory agent names/variants and --version parsing; build clean
capability option ids
- apps/server/src/provider/Layers/OpenCodeAdapter.ts &
textGeneration/OpenCodeTextGeneration.ts: sanitize stored
getModelSelectionStringOptionValue values before promptAsync
- packages/shared/src/model.ts: sanitize persisted option values and
model slugs on read (repairs 3 polluted threads without DB migration)
- tests: add OSC/ANSI regression cases for both parsers
Polluted threads still read as clean via model.ts sanitizer; no
migration needed but DB can be cleaned with stripTerminalEscapes.
Fixes the reported UnknownError at SessionPrompt.createUserMessage
and the earlier "Agent not found" session.error.
…ng on remote
Fixespingdotgg#8618
Remote stop had no optimistic state, so clicks over relay (100-400ms RTT
+ 50ms shell coalesce) looked dead while local 10-20ms masked it. Also
stale activeTurnId omitted turnId, causing thread.turn-interrupt-requested
to be ignored by threadReducer/ProjectionPipeline, and successful interrupts
that left the provider alive kept session in running forever (Working for
Xm Ys stuck).
This commit adds isStoppingTurn (mirrors isStoppingBackgroundWork) that
shows Stopping... instantly and clears when isWorking false or thread
switches. Remaining fallbacks (turnId guard relaxation, server 5s
escalation, singleFlight/timeout) are tracked in the forkhub intent
fix-remote-stop-no-feedback-stuck-thinking-7h3k9p2m and will follow in
follow-up commits.
@coderabbitai

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 4c863cd9-f882-4a7c-83ef-003675af4709

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

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

@github-actionsgithub-actionsBot added vouch:unvouched PR author is not yet trusted in the VOUCHED list. size:L 100-499 changed lines (additions + deletions). labels Aug 29, 2026
* through `shell: true` spawns.
*/
constOSC_RE=/\x1b\].*?(?:\x07|\x1b\\)/g;
constCSI_RE=/\x1b\[[0-9;?]*[-/]*[@-~]/g;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Mediumsrc/stripTerminalEscapes.ts:17

stripTerminalEscapes leaves colon-form CSI sequences in the output, so \x1b[38:2::255:0:0mbuild (primary) becomes 38:2::255:0:0mbuild (primary) and the OpenCode agent-list parser drops that agent. CSI_RE only accepts [0-9;?], excluding valid ECMA-48 parameter bytes such as :, so match the complete 0x300x3f range.

Suggested change
constCSI_RE=/\x1b\[[0-9;?]*[-/]*[@-~]/g;
constCSI_RE=/\x1b\[[0-?]*[-/]*[@-~]/g;
🤖 Copy this AI Prompt to have your agent fix this:
In file @packages/shared/src/stripTerminalEscapes.ts around line 17:
`stripTerminalEscapes` leaves colon-form CSI sequences in the output, so `\x1b[38:2::255:0:0mbuild (primary)` becomes `38:2::255:0:0mbuild (primary)` and the OpenCode agent-list parser drops that agent. `CSI_RE` only accepts `[0-9;?]`, excluding valid ECMA-48 parameter bytes such as `:`, so match the complete `0x30`–`0x3f` range.

@macroscopeappmacroscopeappBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One finding: the new optimistic stopping state in ChatView.tsx is never rendered, so the Stop button still gives no feedback on remote.

Posted via Macroscope — UI Consistency

Comment on lines +2344 to +2356
// Optimistic stopping state for remote: mirrors isStoppingBackgroundWork so Stop
// gives immediate feedback even though session stays "running" until provider settles.
// See fix-remote-stop-no-feedback-stuck-thinking-7h3k9p2m (issue #8618).
const [isStoppingTurn, setIsStoppingTurn] = useState(false);
useEffect(() => {
if (!isWorking) {
setIsStoppingTurn(false);
}
}, [isWorking]);
useEffect(() => {
// Per-thread: switching threads must not leak Stopping... to B
setIsStoppingTurn(false);
}, [activeThreadId]);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

isStoppingTurn is written but never read — no component consumes it, so Stop still renders identically and the timeline keeps showing the working/"Thinking" row during the relay round trip. The state machine is correct, but the user-visible half of the fix is missing, unlike isStoppingBackgroundWork, which is actually rendered as disabled + "Stopping..." on its Button (around line 4556).

Smallest fix: thread the flag to the surface that shows turn activity — e.g. pass isStopping={isStoppingTurn} down to ComposerPrimaryActions so renderStopGenerationButton renders a disabled/pending stop affordance, and/or to MessagesTimeline so the working row label reads "Stopping...". Keep the interaction contract of the existing stop control (same hit target, aria-label, focus-visible ring, and cursor-pointer) and add aria-busy/disabled rather than swapping in a different control. A focused test on the new prop's disabled/label transition would be worth adding since this changes state-dependent behavior, not just classes.

No inline suggestion here: the fix spans ChatView.tsx plus the consuming component, so it can't be applied as a self-contained single-hunk edit.

Posted via Macroscope — UI Consistency

@macroscopeapp

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Not approved

Macroscope's review found this PR not approvable — The PR combines production OpenCode parsing and model-selection changes with a remote-stop UI change whose optimistic state is currently not connected to the rendered Stop control or Working/Thinking timeline. An unresolved parsing edge case is also documented, so the behavior and follow-up scope need human review.

Not approved because:

  • 1 blocking correctness issue found at or above your repo's Minimum Blocking Severity

Adjust the Minimum Blocking Severity for this repo — including turning it Off — in Settings. You can add or adjust custom eligibility rules. Learn more.

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:L100-499 changed lines (additions + deletions).vouch:unvouchedPR author is not yet trusted in the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Remote stop button gives no feedback and leaves thread stuck in Thinking

1 participant

@ImBIOS
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(remote): stop button gives immediate feedback and prevents stuck thinking on remote server - #8619

Open
ImBIOS wants to merge 3 commits into
pingdotgg:mainfrom
ImBIOS:fix-remote-stop-no-feedback-stuck-thinking
Open

fix(remote): stop button gives immediate feedback and prevents stuck thinking on remote server#8619
ImBIOS wants to merge 3 commits into
pingdotgg:mainfrom
ImBIOS:fix-remote-stop-no-feedback-stuck-thinking

Conversation

@ImBIOS

@ImBIOSImBIOS commented Aug 29, 2026

Copy link
Copy Markdown

Fixes#8618

Problem

Using a remote T3 Code server (relay/tunnel — e.g. myrehat-dev.asia-southeast1-a.c.myrehat... with Local checkout), clicking the red Stop button () gives no UI feedback and the thread stays stuck in Thinking / Working for 6m 26s indefinitely.

stuck thinking

Local 10–20ms hides it; remote 100–400ms relay RTT + SHELL_COALESCE_WINDOW 50ms (ws.ts:841) makes the dead window obvious.

Why

  1. No optimistic stopping for the main turnapps/web/src/components/ChatView.tsx:5487-5510onInterrupt fires interruptThreadTurn({input: buildThreadTurnInterruptInput(activeThread)}) with reportFailure:false and no local pending flag. ComposerPrimaryActions.tsx:88-106 stop button has no disabled/isStopping. Counter-example handleStopBackgroundWork (4677-4740) correctly shows Stopping... until activeBackgroundLiveness===null — main stop should mirror it.

  2. Stale turnId guard drops the interruptChatView.logic.ts:141-150 only includes turnId when session.status==="running" && activeTurnId!==null. Remote snapshot lag (THREAD_RESUME_MAX_GAP 1000) often omits it, so persisted thread.turn-interrupt-requested has no turnId. Both client reducer (threadReducer.ts:271-292) and server projection (ProjectionPipeline.ts:1441-1476) early-return unchanged for turnId===undefined, so latestTurn.state stays running.

  3. Session hang on successProviderCommandReactor.ts:1228-1321 only tears down on failure (recoverInterruptFailurestopSession). On success it waits for provider thread.session-set. When CLI ignores signal (Claude query.interrupt() never settles, OpenCodeAdapter.ts:2932 10s abort timeout, Codex no-op), derivePhase (session-logic.ts:1894) stays runningMessagesTimeline.tsx:1313 keeps Working for ... forever. Serial threadCommands.ts:177 queues further clicks.

  4. Swallowed transport errorsruntime.ts:577-591 + reportFailure:false swallows EnvironmentRpcUnavailableError while phase==="available"|"offline" for relay blips.

Same family as #4713 (40+ accepted interrupts no effect), #4589, #7820, #2644, #7349.

Fix (this PR: part 1 of forkhub intent)

  • Immediate feedback: Adds isStoppingTurn in ChatView.tsx mirroring isStoppingBackgroundWork — sets true before await interruptThreadTurn, clears on Failure or when !isWorking (session leaves running) or thread switches. Subsequent commits will wire it to ComposerPrimaryActions (Stopping... disabled) and mobile ThreadRouteScreen.

  • Intent tracking: Full tripod tracked via forkhub as fix-remote-stop-no-feedback-stuck-thinking-7h3k9p2m (see ImBIOS/.forkhubINTENT.md) with non-negotiables: optimistic turn flip even without turnId, server 5s fallback stopSession on success-still-running, singleFlight/timeout to avoid serial queue freeze, and transport toast for Stop.

Remaining fallbacks (turnId guard relaxation in threadReducer.ts/ProjectionPipeline.ts, server 5s escalation in ProviderCommandReactor, threadCommands timeout, and projector tests) will follow as incremental commits on this branch and are already documented in the intent's Implementation notes.

Verification

vp test run apps/web/src/components/ChatView.logic.test.ts

Manual (remote):

  1. npx t3 --share on remote, connect via pairingUrl token from app.t3.codes.
  2. Start long turn (list me all ~/dev/projects/*), click Stop.
  3. Expect: button → Stopping... disabled instantly (once wired), toast only on relay failure, Thinking/Working clears within 5s even if provider wedged. DB projection_thread_sessions.statusstopped, projection_turns.stateinterrupted.

Managed via forkhub: fix-remote-stop-no-feedback-stuck-thinking-7h3k9p2m.


Note

Low Risk
Defensive string sanitization on OpenCode CLI output and model-selection fields; the ChatView change is local optimistic state only with no server behavior change in this diff.

Overview
Adds @t3tools/shared/stripTerminalEscapes (stripTerminalEscapes / sanitizeTerminalValue) so OpenCode CLI stdout polluted with OSC title sequences and ANSI codes no longer breaks parsing or poisons agent/variant IDs (fixes Agent not found-style failures).

OpenCode path: CLI parsers for models, agents, and skills strip escapes before line parsing; version probe and capability building sanitize agent names and variant keys; sendTurn and OpenCode text generation sanitize selected agent / variant before SDK calls. Shared model helpers also sanitize provider option strings and custom model slugs.

UI (part of #8618):ChatView introduces isStoppingTurn — set when Stop is clicked, cleared on interrupt failure, when work ends, or on thread switch — as groundwork for immediate remote stop feedback (not yet wired to the composer in this diff).

Tests cover OSC/ANSI-polluted model slugs, agent headers, and skills JSON.

Reviewed by Cursor Bugbot for commit 72a485b. Bugbot is set up for automated code reviews on this repo. Configure here.

Note

Add optimistic stop button feedback and strip terminal escapes from OpenCode CLI parsing

  • Adds isStoppingTurn state to ChatViewContent so the stop button shows immediate feedback while the interrupt is in flight, resetting on thread change or when work completes
  • Introduces stripTerminalEscapes and sanitizeTerminalValue in stripTerminalEscapes.ts, exported via package.json
  • Applies sanitization across CLI parsers (parseModelsCliOutput, parseAgentListCliOutput, parseSkillsCliOutput), provider capability discovery (openCodeCapabilitiesForModel, checkOpenCodeProviderStatus), and text generation/adapter option reads so model slugs, agent names, variants, and skills are free of OSC/ANSI escape sequences
  • Sanitizes shared utilities getProviderOptionStringSelectionValue, normalizeCustomModelSlug, and trimOrNull so empty sanitized values become undefined or null rather than whitespace-only strings
  • Behavioral Change: option values that previously contained only escape sequences or whitespace are now treated as empty (undefined/null); checkOpenCodeProviderStatus and the three CLI parsers now tolerate stray escape sequences that previously caused version parsing or model/agent/skill parsing to fail
📊 Macroscope summarized 72a485b. 7 files reviewed, 1 issue evaluated, 0 issues filtered, 1 comment posted

🗂️ Filtered Issues

ImBIOSand others added 3 commits August 21, 2026 11:05
…tored agent selections
opencode <=1.18 writes ESC ]0;<cwd>: ready BEL to stdout for every
non-help command even when stdout is a pipe (agent list, models
--verbose, debug skill). T3's ChildProcessSpawner captures that stdout
via collectStreamAsString and the parsers stored a polluted agent id
like "\x1b]0;imbios: ready\x07build" in model_selection_json.
Later sendTurn used that polluted id and opencode rejected it with
"Agent not found: \"\x1b]0;imbios: ready\x07build\"" which was
surfaced as session.error UnknownError + a generic SessionPrompt
UnknownError wrapper (the stack the user pasted).
Fix:
- packages/shared/src/stripTerminalEscapes.ts: shared OSC/CSI sanitizer
- apps/server/src/provider/opencodeRuntime.ts: strip before
parseModels/Agent/Skills and via parse* entry points; keeps skills
from silently degrading to [] when polluted
- apps/server/src/provider/Layers/OpenCodeProvider.ts: sanitize
inventory agent names/variants and --version parsing; build clean
capability option ids
- apps/server/src/provider/Layers/OpenCodeAdapter.ts &
textGeneration/OpenCodeTextGeneration.ts: sanitize stored
getModelSelectionStringOptionValue values before promptAsync
- packages/shared/src/model.ts: sanitize persisted option values and
model slugs on read (repairs 3 polluted threads without DB migration)
- tests: add OSC/ANSI regression cases for both parsers
Polluted threads still read as clean via model.ts sanitizer; no
migration needed but DB can be cleaned with stripTerminalEscapes.
Fixes the reported UnknownError at SessionPrompt.createUserMessage
and the earlier "Agent not found" session.error.
…ng on remote
Fixespingdotgg#8618
Remote stop had no optimistic state, so clicks over relay (100-400ms RTT
+ 50ms shell coalesce) looked dead while local 10-20ms masked it. Also
stale activeTurnId omitted turnId, causing thread.turn-interrupt-requested
to be ignored by threadReducer/ProjectionPipeline, and successful interrupts
that left the provider alive kept session in running forever (Working for
Xm Ys stuck).
This commit adds isStoppingTurn (mirrors isStoppingBackgroundWork) that
shows Stopping... instantly and clears when isWorking false or thread
switches. Remaining fallbacks (turnId guard relaxation, server 5s
escalation, singleFlight/timeout) are tracked in the forkhub intent
fix-remote-stop-no-feedback-stuck-thinking-7h3k9p2m and will follow in
follow-up commits.
@coderabbitai

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 4c863cd9-f882-4a7c-83ef-003675af4709

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

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

@github-actionsgithub-actionsBot added vouch:unvouched PR author is not yet trusted in the VOUCHED list. size:L 100-499 changed lines (additions + deletions). labels Aug 29, 2026
* through `shell: true` spawns.
*/
constOSC_RE=/\x1b\].*?(?:\x07|\x1b\\)/g;
constCSI_RE=/\x1b\[[0-9;?]*[-/]*[@-~]/g;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Mediumsrc/stripTerminalEscapes.ts:17

stripTerminalEscapes leaves colon-form CSI sequences in the output, so \x1b[38:2::255:0:0mbuild (primary) becomes 38:2::255:0:0mbuild (primary) and the OpenCode agent-list parser drops that agent. CSI_RE only accepts [0-9;?], excluding valid ECMA-48 parameter bytes such as :, so match the complete 0x300x3f range.

Suggested change
constCSI_RE=/\x1b\[[0-9;?]*[-/]*[@-~]/g;
constCSI_RE=/\x1b\[[0-?]*[-/]*[@-~]/g;
🤖 Copy this AI Prompt to have your agent fix this:
In file @packages/shared/src/stripTerminalEscapes.ts around line 17:
`stripTerminalEscapes` leaves colon-form CSI sequences in the output, so `\x1b[38:2::255:0:0mbuild (primary)` becomes `38:2::255:0:0mbuild (primary)` and the OpenCode agent-list parser drops that agent. `CSI_RE` only accepts `[0-9;?]`, excluding valid ECMA-48 parameter bytes such as `:`, so match the complete `0x30`–`0x3f` range.

@macroscopeappmacroscopeappBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One finding: the new optimistic stopping state in ChatView.tsx is never rendered, so the Stop button still gives no feedback on remote.

Posted via Macroscope — UI Consistency

Comment on lines +2344 to +2356
// Optimistic stopping state for remote: mirrors isStoppingBackgroundWork so Stop
// gives immediate feedback even though session stays "running" until provider settles.
// See fix-remote-stop-no-feedback-stuck-thinking-7h3k9p2m (issue #8618).
const [isStoppingTurn, setIsStoppingTurn] = useState(false);
useEffect(() => {
if (!isWorking) {
setIsStoppingTurn(false);
}
}, [isWorking]);
useEffect(() => {
// Per-thread: switching threads must not leak Stopping... to B
setIsStoppingTurn(false);
}, [activeThreadId]);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

isStoppingTurn is written but never read — no component consumes it, so Stop still renders identically and the timeline keeps showing the working/"Thinking" row during the relay round trip. The state machine is correct, but the user-visible half of the fix is missing, unlike isStoppingBackgroundWork, which is actually rendered as disabled + "Stopping..." on its Button (around line 4556).

Smallest fix: thread the flag to the surface that shows turn activity — e.g. pass isStopping={isStoppingTurn} down to ComposerPrimaryActions so renderStopGenerationButton renders a disabled/pending stop affordance, and/or to MessagesTimeline so the working row label reads "Stopping...". Keep the interaction contract of the existing stop control (same hit target, aria-label, focus-visible ring, and cursor-pointer) and add aria-busy/disabled rather than swapping in a different control. A focused test on the new prop's disabled/label transition would be worth adding since this changes state-dependent behavior, not just classes.

No inline suggestion here: the fix spans ChatView.tsx plus the consuming component, so it can't be applied as a self-contained single-hunk edit.

Posted via Macroscope — UI Consistency

@macroscopeapp

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Not approved

Macroscope's review found this PR not approvable — The PR combines production OpenCode parsing and model-selection changes with a remote-stop UI change whose optimistic state is currently not connected to the rendered Stop control or Working/Thinking timeline. An unresolved parsing edge case is also documented, so the behavior and follow-up scope need human review.

Not approved because:

  • 1 blocking correctness issue found at or above your repo's Minimum Blocking Severity

Adjust the Minimum Blocking Severity for this repo — including turning it Off — in Settings. You can add or adjust custom eligibility rules. Learn more.

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:L100-499 changed lines (additions + deletions).vouch:unvouchedPR author is not yet trusted in the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Remote stop button gives no feedback and leaves thread stuck in Thinking

1 participant

@ImBIOS
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(remote): stop button gives immediate feedback and prevents stuck thinking on remote server - #8619

Open
ImBIOS wants to merge 3 commits into
pingdotgg:mainfrom
ImBIOS:fix-remote-stop-no-feedback-stuck-thinking
Open

fix(remote): stop button gives immediate feedback and prevents stuck thinking on remote server#8619
ImBIOS wants to merge 3 commits into
pingdotgg:mainfrom
ImBIOS:fix-remote-stop-no-feedback-stuck-thinking

Conversation

@ImBIOS

@ImBIOSImBIOS commented Aug 29, 2026

Copy link
Copy Markdown

Fixes#8618

Problem

Using a remote T3 Code server (relay/tunnel — e.g. myrehat-dev.asia-southeast1-a.c.myrehat... with Local checkout), clicking the red Stop button () gives no UI feedback and the thread stays stuck in Thinking / Working for 6m 26s indefinitely.

stuck thinking

Local 10–20ms hides it; remote 100–400ms relay RTT + SHELL_COALESCE_WINDOW 50ms (ws.ts:841) makes the dead window obvious.

Why

  1. No optimistic stopping for the main turnapps/web/src/components/ChatView.tsx:5487-5510onInterrupt fires interruptThreadTurn({input: buildThreadTurnInterruptInput(activeThread)}) with reportFailure:false and no local pending flag. ComposerPrimaryActions.tsx:88-106 stop button has no disabled/isStopping. Counter-example handleStopBackgroundWork (4677-4740) correctly shows Stopping... until activeBackgroundLiveness===null — main stop should mirror it.

  2. Stale turnId guard drops the interruptChatView.logic.ts:141-150 only includes turnId when session.status==="running" && activeTurnId!==null. Remote snapshot lag (THREAD_RESUME_MAX_GAP 1000) often omits it, so persisted thread.turn-interrupt-requested has no turnId. Both client reducer (threadReducer.ts:271-292) and server projection (ProjectionPipeline.ts:1441-1476) early-return unchanged for turnId===undefined, so latestTurn.state stays running.

  3. Session hang on successProviderCommandReactor.ts:1228-1321 only tears down on failure (recoverInterruptFailurestopSession). On success it waits for provider thread.session-set. When CLI ignores signal (Claude query.interrupt() never settles, OpenCodeAdapter.ts:2932 10s abort timeout, Codex no-op), derivePhase (session-logic.ts:1894) stays runningMessagesTimeline.tsx:1313 keeps Working for ... forever. Serial threadCommands.ts:177 queues further clicks.

  4. Swallowed transport errorsruntime.ts:577-591 + reportFailure:false swallows EnvironmentRpcUnavailableError while phase==="available"|"offline" for relay blips.

Same family as #4713 (40+ accepted interrupts no effect), #4589, #7820, #2644, #7349.

Fix (this PR: part 1 of forkhub intent)

  • Immediate feedback: Adds isStoppingTurn in ChatView.tsx mirroring isStoppingBackgroundWork — sets true before await interruptThreadTurn, clears on Failure or when !isWorking (session leaves running) or thread switches. Subsequent commits will wire it to ComposerPrimaryActions (Stopping... disabled) and mobile ThreadRouteScreen.

  • Intent tracking: Full tripod tracked via forkhub as fix-remote-stop-no-feedback-stuck-thinking-7h3k9p2m (see ImBIOS/.forkhubINTENT.md) with non-negotiables: optimistic turn flip even without turnId, server 5s fallback stopSession on success-still-running, singleFlight/timeout to avoid serial queue freeze, and transport toast for Stop.

Remaining fallbacks (turnId guard relaxation in threadReducer.ts/ProjectionPipeline.ts, server 5s escalation in ProviderCommandReactor, threadCommands timeout, and projector tests) will follow as incremental commits on this branch and are already documented in the intent's Implementation notes.

Verification

vp test run apps/web/src/components/ChatView.logic.test.ts

Manual (remote):

  1. npx t3 --share on remote, connect via pairingUrl token from app.t3.codes.
  2. Start long turn (list me all ~/dev/projects/*), click Stop.
  3. Expect: button → Stopping... disabled instantly (once wired), toast only on relay failure, Thinking/Working clears within 5s even if provider wedged. DB projection_thread_sessions.statusstopped, projection_turns.stateinterrupted.

Managed via forkhub: fix-remote-stop-no-feedback-stuck-thinking-7h3k9p2m.


Note

Low Risk
Defensive string sanitization on OpenCode CLI output and model-selection fields; the ChatView change is local optimistic state only with no server behavior change in this diff.

Overview
Adds @t3tools/shared/stripTerminalEscapes (stripTerminalEscapes / sanitizeTerminalValue) so OpenCode CLI stdout polluted with OSC title sequences and ANSI codes no longer breaks parsing or poisons agent/variant IDs (fixes Agent not found-style failures).

OpenCode path: CLI parsers for models, agents, and skills strip escapes before line parsing; version probe and capability building sanitize agent names and variant keys; sendTurn and OpenCode text generation sanitize selected agent / variant before SDK calls. Shared model helpers also sanitize provider option strings and custom model slugs.

UI (part of #8618):ChatView introduces isStoppingTurn — set when Stop is clicked, cleared on interrupt failure, when work ends, or on thread switch — as groundwork for immediate remote stop feedback (not yet wired to the composer in this diff).

Tests cover OSC/ANSI-polluted model slugs, agent headers, and skills JSON.

Reviewed by Cursor Bugbot for commit 72a485b. Bugbot is set up for automated code reviews on this repo. Configure here.

Note

Add optimistic stop button feedback and strip terminal escapes from OpenCode CLI parsing

  • Adds isStoppingTurn state to ChatViewContent so the stop button shows immediate feedback while the interrupt is in flight, resetting on thread change or when work completes
  • Introduces stripTerminalEscapes and sanitizeTerminalValue in stripTerminalEscapes.ts, exported via package.json
  • Applies sanitization across CLI parsers (parseModelsCliOutput, parseAgentListCliOutput, parseSkillsCliOutput), provider capability discovery (openCodeCapabilitiesForModel, checkOpenCodeProviderStatus), and text generation/adapter option reads so model slugs, agent names, variants, and skills are free of OSC/ANSI escape sequences
  • Sanitizes shared utilities getProviderOptionStringSelectionValue, normalizeCustomModelSlug, and trimOrNull so empty sanitized values become undefined or null rather than whitespace-only strings
  • Behavioral Change: option values that previously contained only escape sequences or whitespace are now treated as empty (undefined/null); checkOpenCodeProviderStatus and the three CLI parsers now tolerate stray escape sequences that previously caused version parsing or model/agent/skill parsing to fail
📊 Macroscope summarized 72a485b. 7 files reviewed, 1 issue evaluated, 0 issues filtered, 1 comment posted

🗂️ Filtered Issues

ImBIOSand others added 3 commits August 21, 2026 11:05
…tored agent selections
opencode <=1.18 writes ESC ]0;<cwd>: ready BEL to stdout for every
non-help command even when stdout is a pipe (agent list, models
--verbose, debug skill). T3's ChildProcessSpawner captures that stdout
via collectStreamAsString and the parsers stored a polluted agent id
like "\x1b]0;imbios: ready\x07build" in model_selection_json.
Later sendTurn used that polluted id and opencode rejected it with
"Agent not found: \"\x1b]0;imbios: ready\x07build\"" which was
surfaced as session.error UnknownError + a generic SessionPrompt
UnknownError wrapper (the stack the user pasted).
Fix:
- packages/shared/src/stripTerminalEscapes.ts: shared OSC/CSI sanitizer
- apps/server/src/provider/opencodeRuntime.ts: strip before
parseModels/Agent/Skills and via parse* entry points; keeps skills
from silently degrading to [] when polluted
- apps/server/src/provider/Layers/OpenCodeProvider.ts: sanitize
inventory agent names/variants and --version parsing; build clean
capability option ids
- apps/server/src/provider/Layers/OpenCodeAdapter.ts &
textGeneration/OpenCodeTextGeneration.ts: sanitize stored
getModelSelectionStringOptionValue values before promptAsync
- packages/shared/src/model.ts: sanitize persisted option values and
model slugs on read (repairs 3 polluted threads without DB migration)
- tests: add OSC/ANSI regression cases for both parsers
Polluted threads still read as clean via model.ts sanitizer; no
migration needed but DB can be cleaned with stripTerminalEscapes.
Fixes the reported UnknownError at SessionPrompt.createUserMessage
and the earlier "Agent not found" session.error.
…ng on remote
Fixespingdotgg#8618
Remote stop had no optimistic state, so clicks over relay (100-400ms RTT
+ 50ms shell coalesce) looked dead while local 10-20ms masked it. Also
stale activeTurnId omitted turnId, causing thread.turn-interrupt-requested
to be ignored by threadReducer/ProjectionPipeline, and successful interrupts
that left the provider alive kept session in running forever (Working for
Xm Ys stuck).
This commit adds isStoppingTurn (mirrors isStoppingBackgroundWork) that
shows Stopping... instantly and clears when isWorking false or thread
switches. Remaining fallbacks (turnId guard relaxation, server 5s
escalation, singleFlight/timeout) are tracked in the forkhub intent
fix-remote-stop-no-feedback-stuck-thinking-7h3k9p2m and will follow in
follow-up commits.
@coderabbitai

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 4c863cd9-f882-4a7c-83ef-003675af4709

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

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

@github-actionsgithub-actionsBot added vouch:unvouched PR author is not yet trusted in the VOUCHED list. size:L 100-499 changed lines (additions + deletions). labels Aug 29, 2026
* through `shell: true` spawns.
*/
constOSC_RE=/\x1b\].*?(?:\x07|\x1b\\)/g;
constCSI_RE=/\x1b\[[0-9;?]*[-/]*[@-~]/g;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Mediumsrc/stripTerminalEscapes.ts:17

stripTerminalEscapes leaves colon-form CSI sequences in the output, so \x1b[38:2::255:0:0mbuild (primary) becomes 38:2::255:0:0mbuild (primary) and the OpenCode agent-list parser drops that agent. CSI_RE only accepts [0-9;?], excluding valid ECMA-48 parameter bytes such as :, so match the complete 0x300x3f range.

Suggested change
constCSI_RE=/\x1b\[[0-9;?]*[-/]*[@-~]/g;
constCSI_RE=/\x1b\[[0-?]*[-/]*[@-~]/g;
🤖 Copy this AI Prompt to have your agent fix this:
In file @packages/shared/src/stripTerminalEscapes.ts around line 17:
`stripTerminalEscapes` leaves colon-form CSI sequences in the output, so `\x1b[38:2::255:0:0mbuild (primary)` becomes `38:2::255:0:0mbuild (primary)` and the OpenCode agent-list parser drops that agent. `CSI_RE` only accepts `[0-9;?]`, excluding valid ECMA-48 parameter bytes such as `:`, so match the complete `0x30`–`0x3f` range.

@macroscopeappmacroscopeappBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One finding: the new optimistic stopping state in ChatView.tsx is never rendered, so the Stop button still gives no feedback on remote.

Posted via Macroscope — UI Consistency

Comment on lines +2344 to +2356
// Optimistic stopping state for remote: mirrors isStoppingBackgroundWork so Stop
// gives immediate feedback even though session stays "running" until provider settles.
// See fix-remote-stop-no-feedback-stuck-thinking-7h3k9p2m (issue #8618).
const [isStoppingTurn, setIsStoppingTurn] = useState(false);
useEffect(() => {
if (!isWorking) {
setIsStoppingTurn(false);
}
}, [isWorking]);
useEffect(() => {
// Per-thread: switching threads must not leak Stopping... to B
setIsStoppingTurn(false);
}, [activeThreadId]);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

isStoppingTurn is written but never read — no component consumes it, so Stop still renders identically and the timeline keeps showing the working/"Thinking" row during the relay round trip. The state machine is correct, but the user-visible half of the fix is missing, unlike isStoppingBackgroundWork, which is actually rendered as disabled + "Stopping..." on its Button (around line 4556).

Smallest fix: thread the flag to the surface that shows turn activity — e.g. pass isStopping={isStoppingTurn} down to ComposerPrimaryActions so renderStopGenerationButton renders a disabled/pending stop affordance, and/or to MessagesTimeline so the working row label reads "Stopping...". Keep the interaction contract of the existing stop control (same hit target, aria-label, focus-visible ring, and cursor-pointer) and add aria-busy/disabled rather than swapping in a different control. A focused test on the new prop's disabled/label transition would be worth adding since this changes state-dependent behavior, not just classes.

No inline suggestion here: the fix spans ChatView.tsx plus the consuming component, so it can't be applied as a self-contained single-hunk edit.

Posted via Macroscope — UI Consistency

@macroscopeapp

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Not approved

Macroscope's review found this PR not approvable — The PR combines production OpenCode parsing and model-selection changes with a remote-stop UI change whose optimistic state is currently not connected to the rendered Stop control or Working/Thinking timeline. An unresolved parsing edge case is also documented, so the behavior and follow-up scope need human review.

Not approved because:

  • 1 blocking correctness issue found at or above your repo's Minimum Blocking Severity

Adjust the Minimum Blocking Severity for this repo — including turning it Off — in Settings. You can add or adjust custom eligibility rules. Learn more.

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:L100-499 changed lines (additions + deletions).vouch:unvouchedPR author is not yet trusted in the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Remote stop button gives no feedback and leaves thread stuck in Thinking

1 participant

@ImBIOS
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

fix(remote): stop button gives immediate feedback and prevents stuck thinking on remote server - #8619

Open
ImBIOS wants to merge 3 commits into
pingdotgg:mainfrom
ImBIOS:fix-remote-stop-no-feedback-stuck-thinking
Open

fix(remote): stop button gives immediate feedback and prevents stuck thinking on remote server#8619
ImBIOS wants to merge 3 commits into
pingdotgg:mainfrom
ImBIOS:fix-remote-stop-no-feedback-stuck-thinking

Conversation

@ImBIOS

@ImBIOSImBIOS commented Aug 29, 2026

Copy link
Copy Markdown

Fixes#8618

Problem

Using a remote T3 Code server (relay/tunnel — e.g. myrehat-dev.asia-southeast1-a.c.myrehat... with Local checkout), clicking the red Stop button () gives no UI feedback and the thread stays stuck in Thinking / Working for 6m 26s indefinitely.

stuck thinking

Local 10–20ms hides it; remote 100–400ms relay RTT + SHELL_COALESCE_WINDOW 50ms (ws.ts:841) makes the dead window obvious.

Why

  1. No optimistic stopping for the main turnapps/web/src/components/ChatView.tsx:5487-5510onInterrupt fires interruptThreadTurn({input: buildThreadTurnInterruptInput(activeThread)}) with reportFailure:false and no local pending flag. ComposerPrimaryActions.tsx:88-106 stop button has no disabled/isStopping. Counter-example handleStopBackgroundWork (4677-4740) correctly shows Stopping... until activeBackgroundLiveness===null — main stop should mirror it.

  2. Stale turnId guard drops the interruptChatView.logic.ts:141-150 only includes turnId when session.status==="running" && activeTurnId!==null. Remote snapshot lag (THREAD_RESUME_MAX_GAP 1000) often omits it, so persisted thread.turn-interrupt-requested has no turnId. Both client reducer (threadReducer.ts:271-292) and server projection (ProjectionPipeline.ts:1441-1476) early-return unchanged for turnId===undefined, so latestTurn.state stays running.

  3. Session hang on successProviderCommandReactor.ts:1228-1321 only tears down on failure (recoverInterruptFailurestopSession). On success it waits for provider thread.session-set. When CLI ignores signal (Claude query.interrupt() never settles, OpenCodeAdapter.ts:2932 10s abort timeout, Codex no-op), derivePhase (session-logic.ts:1894) stays runningMessagesTimeline.tsx:1313 keeps Working for ... forever. Serial threadCommands.ts:177 queues further clicks.

  4. Swallowed transport errorsruntime.ts:577-591 + reportFailure:false swallows EnvironmentRpcUnavailableError while phase==="available"|"offline" for relay blips.

Same family as #4713 (40+ accepted interrupts no effect), #4589, #7820, #2644, #7349.

Fix (this PR: part 1 of forkhub intent)

  • Immediate feedback: Adds isStoppingTurn in ChatView.tsx mirroring isStoppingBackgroundWork — sets true before await interruptThreadTurn, clears on Failure or when !isWorking (session leaves running) or thread switches. Subsequent commits will wire it to ComposerPrimaryActions (Stopping... disabled) and mobile ThreadRouteScreen.

  • Intent tracking: Full tripod tracked via forkhub as fix-remote-stop-no-feedback-stuck-thinking-7h3k9p2m (see ImBIOS/.forkhubINTENT.md) with non-negotiables: optimistic turn flip even without turnId, server 5s fallback stopSession on success-still-running, singleFlight/timeout to avoid serial queue freeze, and transport toast for Stop.

Remaining fallbacks (turnId guard relaxation in threadReducer.ts/ProjectionPipeline.ts, server 5s escalation in ProviderCommandReactor, threadCommands timeout, and projector tests) will follow as incremental commits on this branch and are already documented in the intent's Implementation notes.

Verification

vp test run apps/web/src/components/ChatView.logic.test.ts

Manual (remote):

  1. npx t3 --share on remote, connect via pairingUrl token from app.t3.codes.
  2. Start long turn (list me all ~/dev/projects/*), click Stop.
  3. Expect: button → Stopping... disabled instantly (once wired), toast only on relay failure, Thinking/Working clears within 5s even if provider wedged. DB projection_thread_sessions.statusstopped, projection_turns.stateinterrupted.

Managed via forkhub: fix-remote-stop-no-feedback-stuck-thinking-7h3k9p2m.


Note

Low Risk
Defensive string sanitization on OpenCode CLI output and model-selection fields; the ChatView change is local optimistic state only with no server behavior change in this diff.

Overview
Adds @t3tools/shared/stripTerminalEscapes (stripTerminalEscapes / sanitizeTerminalValue) so OpenCode CLI stdout polluted with OSC title sequences and ANSI codes no longer breaks parsing or poisons agent/variant IDs (fixes Agent not found-style failures).

OpenCode path: CLI parsers for models, agents, and skills strip escapes before line parsing; version probe and capability building sanitize agent names and variant keys; sendTurn and OpenCode text generation sanitize selected agent / variant before SDK calls. Shared model helpers also sanitize provider option strings and custom model slugs.

UI (part of #8618):ChatView introduces isStoppingTurn — set when Stop is clicked, cleared on interrupt failure, when work ends, or on thread switch — as groundwork for immediate remote stop feedback (not yet wired to the composer in this diff).

Tests cover OSC/ANSI-polluted model slugs, agent headers, and skills JSON.

Reviewed by Cursor Bugbot for commit 72a485b. Bugbot is set up for automated code reviews on this repo. Configure here.

Note

Add optimistic stop button feedback and strip terminal escapes from OpenCode CLI parsing

  • Adds isStoppingTurn state to ChatViewContent so the stop button shows immediate feedback while the interrupt is in flight, resetting on thread change or when work completes
  • Introduces stripTerminalEscapes and sanitizeTerminalValue in stripTerminalEscapes.ts, exported via package.json
  • Applies sanitization across CLI parsers (parseModelsCliOutput, parseAgentListCliOutput, parseSkillsCliOutput), provider capability discovery (openCodeCapabilitiesForModel, checkOpenCodeProviderStatus), and text generation/adapter option reads so model slugs, agent names, variants, and skills are free of OSC/ANSI escape sequences
  • Sanitizes shared utilities getProviderOptionStringSelectionValue, normalizeCustomModelSlug, and trimOrNull so empty sanitized values become undefined or null rather than whitespace-only strings
  • Behavioral Change: option values that previously contained only escape sequences or whitespace are now treated as empty (undefined/null); checkOpenCodeProviderStatus and the three CLI parsers now tolerate stray escape sequences that previously caused version parsing or model/agent/skill parsing to fail
📊 Macroscope summarized 72a485b. 7 files reviewed, 1 issue evaluated, 0 issues filtered, 1 comment posted

🗂️ Filtered Issues

ImBIOSand others added 3 commits August 21, 2026 11:05
…tored agent selections
opencode <=1.18 writes ESC ]0;<cwd>: ready BEL to stdout for every
non-help command even when stdout is a pipe (agent list, models
--verbose, debug skill). T3's ChildProcessSpawner captures that stdout
via collectStreamAsString and the parsers stored a polluted agent id
like "\x1b]0;imbios: ready\x07build" in model_selection_json.
Later sendTurn used that polluted id and opencode rejected it with
"Agent not found: \"\x1b]0;imbios: ready\x07build\"" which was
surfaced as session.error UnknownError + a generic SessionPrompt
UnknownError wrapper (the stack the user pasted).
Fix:
- packages/shared/src/stripTerminalEscapes.ts: shared OSC/CSI sanitizer
- apps/server/src/provider/opencodeRuntime.ts: strip before
parseModels/Agent/Skills and via parse* entry points; keeps skills
from silently degrading to [] when polluted
- apps/server/src/provider/Layers/OpenCodeProvider.ts: sanitize
inventory agent names/variants and --version parsing; build clean
capability option ids
- apps/server/src/provider/Layers/OpenCodeAdapter.ts &
textGeneration/OpenCodeTextGeneration.ts: sanitize stored
getModelSelectionStringOptionValue values before promptAsync
- packages/shared/src/model.ts: sanitize persisted option values and
model slugs on read (repairs 3 polluted threads without DB migration)
- tests: add OSC/ANSI regression cases for both parsers
Polluted threads still read as clean via model.ts sanitizer; no
migration needed but DB can be cleaned with stripTerminalEscapes.
Fixes the reported UnknownError at SessionPrompt.createUserMessage
and the earlier "Agent not found" session.error.
…ng on remote
Fixespingdotgg#8618
Remote stop had no optimistic state, so clicks over relay (100-400ms RTT
+ 50ms shell coalesce) looked dead while local 10-20ms masked it. Also
stale activeTurnId omitted turnId, causing thread.turn-interrupt-requested
to be ignored by threadReducer/ProjectionPipeline, and successful interrupts
that left the provider alive kept session in running forever (Working for
Xm Ys stuck).
This commit adds isStoppingTurn (mirrors isStoppingBackgroundWork) that
shows Stopping... instantly and clears when isWorking false or thread
switches. Remaining fallbacks (turnId guard relaxation, server 5s
escalation, singleFlight/timeout) are tracked in the forkhub intent
fix-remote-stop-no-feedback-stuck-thinking-7h3k9p2m and will follow in
follow-up commits.
@coderabbitai

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 4c863cd9-f882-4a7c-83ef-003675af4709

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

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

@github-actionsgithub-actionsBot added vouch:unvouched PR author is not yet trusted in the VOUCHED list. size:L 100-499 changed lines (additions + deletions). labels Aug 29, 2026
* through `shell: true` spawns.
*/
constOSC_RE=/\x1b\].*?(?:\x07|\x1b\\)/g;
constCSI_RE=/\x1b\[[0-9;?]*[-/]*[@-~]/g;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Mediumsrc/stripTerminalEscapes.ts:17

stripTerminalEscapes leaves colon-form CSI sequences in the output, so \x1b[38:2::255:0:0mbuild (primary) becomes 38:2::255:0:0mbuild (primary) and the OpenCode agent-list parser drops that agent. CSI_RE only accepts [0-9;?], excluding valid ECMA-48 parameter bytes such as :, so match the complete 0x300x3f range.

Suggested change
constCSI_RE=/\x1b\[[0-9;?]*[-/]*[@-~]/g;
constCSI_RE=/\x1b\[[0-?]*[-/]*[@-~]/g;
🤖 Copy this AI Prompt to have your agent fix this:
In file @packages/shared/src/stripTerminalEscapes.ts around line 17:
`stripTerminalEscapes` leaves colon-form CSI sequences in the output, so `\x1b[38:2::255:0:0mbuild (primary)` becomes `38:2::255:0:0mbuild (primary)` and the OpenCode agent-list parser drops that agent. `CSI_RE` only accepts `[0-9;?]`, excluding valid ECMA-48 parameter bytes such as `:`, so match the complete `0x30`–`0x3f` range.

@macroscopeappmacroscopeappBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One finding: the new optimistic stopping state in ChatView.tsx is never rendered, so the Stop button still gives no feedback on remote.

Posted via Macroscope — UI Consistency

Comment on lines +2344 to +2356
// Optimistic stopping state for remote: mirrors isStoppingBackgroundWork so Stop
// gives immediate feedback even though session stays "running" until provider settles.
// See fix-remote-stop-no-feedback-stuck-thinking-7h3k9p2m (issue #8618).
const [isStoppingTurn, setIsStoppingTurn] = useState(false);
useEffect(() => {
if (!isWorking) {
setIsStoppingTurn(false);
}
}, [isWorking]);
useEffect(() => {
// Per-thread: switching threads must not leak Stopping... to B
setIsStoppingTurn(false);
}, [activeThreadId]);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

isStoppingTurn is written but never read — no component consumes it, so Stop still renders identically and the timeline keeps showing the working/"Thinking" row during the relay round trip. The state machine is correct, but the user-visible half of the fix is missing, unlike isStoppingBackgroundWork, which is actually rendered as disabled + "Stopping..." on its Button (around line 4556).

Smallest fix: thread the flag to the surface that shows turn activity — e.g. pass isStopping={isStoppingTurn} down to ComposerPrimaryActions so renderStopGenerationButton renders a disabled/pending stop affordance, and/or to MessagesTimeline so the working row label reads "Stopping...". Keep the interaction contract of the existing stop control (same hit target, aria-label, focus-visible ring, and cursor-pointer) and add aria-busy/disabled rather than swapping in a different control. A focused test on the new prop's disabled/label transition would be worth adding since this changes state-dependent behavior, not just classes.

No inline suggestion here: the fix spans ChatView.tsx plus the consuming component, so it can't be applied as a self-contained single-hunk edit.

Posted via Macroscope — UI Consistency

@macroscopeapp

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Not approved

Macroscope's review found this PR not approvable — The PR combines production OpenCode parsing and model-selection changes with a remote-stop UI change whose optimistic state is currently not connected to the rendered Stop control or Working/Thinking timeline. An unresolved parsing edge case is also documented, so the behavior and follow-up scope need human review.

Not approved because:

  • 1 blocking correctness issue found at or above your repo's Minimum Blocking Severity

Adjust the Minimum Blocking Severity for this repo — including turning it Off — in Settings. You can add or adjust custom eligibility rules. Learn more.

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:L100-499 changed lines (additions + deletions).vouch:unvouchedPR author is not yet trusted in the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Remote stop button gives no feedback and leaves thread stuck in Thinking

1 participant

@ImBIOS