Skip to content

/rc Discord bridge: thinking elapsed-status persists; mid-run /rc drops events; /rc <mode> restart silently breaks streaming #974

Open

Description

@pbtrudel

Summary

Three related defects in the built-in Discord /rc remote-control bridge (no user mod involved): 1) The -# ✻ Thinking… Ns elapsed-time status line sometimes persists on a Discord reply, even though the agent is emitting new text and the task has completed. 2) Running /rc while the terminal session is already mid-run opens the Discord thread but no session events are ever forwarded to it. 3) Toggling permission mode with /rc yolo (or any /rc that goes through startRemote) restarts the bridge, after which no further session events are forwarded to Discord. I have local patches for all three (see Additional context). I am happy to file PRs once we agree on the fix direction; this issue is to track the bugs.

Expected Behavior

  1. Thinking… Ns should be cleared as soon as the first text_delta (or any terminal event like run_end / interrupted / run_error) is forwarded, so the user always sees the freshest assistant message without a stale thinking line. 2) Running /rc on a session that already has an in-flight run should attach to that run: subsequent run_start / text_delta / tool_* / run_end events should be forwarded to the Discord thread opened by the new bridge. 3) /rc should seamlessly continue forwarding events. From the user perspective, the Discord thread keeps updating through the mode change — the bridge restart is invisible.

Actual Behavior

Defect 1 — stale Thinking… Ns line. Inside createReplyStream.onEvent, the text_delta branch clears the elapsed-time status (i = void 0) but never calls stopTick(). setStatus(undefined) is the only path that cancels the 10 s elapsed-time refresh timer; text_delta does not. The orphan timer fires once, sees i === undefined, and silently dies — but the previously-emitted message containing the ✻ Thinking… Ns line stays on screen because compose() only suppresses the status line on the next n.update(...), which can be swallowed by createDiscordReply's 1 s debounce, by a Discord 429 (no retry on the channel-edit path), or simply by close() having shipped compose(finalText) while i was still set.

Defect 2 — /rc mid-run never attaches a stream. useRemoteBridge's useEffect subscribes to the session event stream and only calls M(chatId, flag) (the function which creates a fresh ReplyStream and assigns it to h.current) when run_start fires with h.current === null and y.current.shift() returns a queued Discord chat id. When the user runs /rc mid-run: no run_start has been emitted since the bridge was set up (the run is already in flight), y.current is empty (no Discord message has been received yet), so h.current stays null, and every subsequent event hits t && (t.onEvent(e), ...) with t === null — silently dropped.

Defect 3 — /rc restart drops events. /rc yolo calls startRemote, which builds a brand-new bridge, then setActiveRemote(...) stops the old one and assigns the new one. subscribeRemoteStopped then sets h.current = null, f.current = null, y.current = []. The new bridge's ownerChatId is undefined until the WebSocket connects, Discord sends READY, and onReady → announceReady → bridge.announce() → threadId() creates the thread (HTTP call). During that window, every session event hits Defect 2's branch, finds ownerChatId === undefined, and bails — events are dropped until the WS is ready. In practice the user observes messages stop and never resume: the old stream is gone, the new thread is invisible to useRemoteBridge until too late.

Steps to reproduce the issue

Defect 1 — start a session, send a Discord message, observe -# ✻ Thinking… Ns. Have the agent emit text_delta events that should remove the line. The line often stays.

Defect 2 — start a session, wait for the agent to begin a run (run_start fires), then run /rc from the terminal. The Discord thread is opened by onReady but no text_delta, tool_*, run_end, etc. is ever forwarded.

Defect 3 — start a session, send a Discord message, observe flowing updates. From the terminal, run /rc yolo (or /rc auto-accept, /rc plan, etc.). The bridge restarts. New updates stop appearing in the Discord thread.

Command Code Version

1.74.0

Operating System

Linux

Terminal/IDE

command-code (terminal TUI)

Shell

bash

Session file (optional)

No response

Fix prompt (optional)

Please fix the three defects in the bundled Discord /rc bridge inside dist/cli.mjs:

  1. In createReplyStream.onEvent, the text_delta branch must call stopTick() after i = void 0, mirroring setStatus(undefined) so the 10-second elapsed-time refresh timer is cancelled deterministically.

  2. In useRemoteBridge, the events subscription should not only attempt to open a stream on run_start. Whenever an event arrives with h.current === null and y.current is empty but a Discord bridge is active with an ownerChatId, the handler must call M(ownerChatId, true) to attach a fresh ReplyStream to the in-flight run.

  3. In startRemote, after setActiveRemote(...) and before s.start(), eagerly create the Discord thread (so it is ready before the first session event after /rc <mode>). Cleanest upstream fix is to expose either the configured channelId or a whenReady() promise on the bridge so we can wait for the thread to be created without sending a placeholder message. If exposing the API is too much, calling s.announce("<one-line mode notice>") between setActiveRemote(...) and s.start() works as a workaround — the announce call resolves the bridge-owned threadId() promise (HTTP, no WS needed) and sets ownerChatId deterministically. A real message (e.g. -# Mode: <label>) reads better than a ZWSP.

Please add node --check validation in CI for the resulting bundle (the file is minified ESM, so new Function is not a viable parse check).

Additional context

Local patches are at ~/bin/patch-cmd-rc-discord.js. It applies three in-place edits to ~/.nvm/versions/node/v26.7.0/lib/node_modules/command-code/dist/cli.mjs (a backup at cli.mjs.pristine is kept). The bundle is minified, so the patch matches on stable text anchors:

  1. Patch 1 (Issue 1): createReplyStream's text_delta branch gets stopTick() after i = void 0 so the 10 s elapsed-time refresh timer is cancelled deterministically, mirroring setStatus(undefined). Offset ~2446931.

  2. Patch 2 (Issue 2): the run-start check inside the events subscription in useRemoteBridge is generalised so that when any event fires with h.current === null and y.current has no queued Discord chatId, the bridge's ownerChatId is used as a fallback (in follow mode). Offset ~2695914.

  3. Patch 3 (Issue 3): startRemote eagerly calls s.announce(\"-# Mode: ${modeLabel(t)}\") between setActiveRemote(...) and s.start(). This forces threadId() to resolve (HTTP, no WS needed) so ownerChatId is defined before any session event arrives. The deliver puts a one-line mode label into the thread so the user sees what mode they are in and Discord stops logging the old "Cannot send an empty message" error from an earlier revision. Offset ~2487983.

All three together restore the expected behaviour.

Activity

  1. added theissue type on Oct 2, 2026
  2. ObaidNadeem commented on Oct 9, 2026

    @ObaidNadeem
    Contributor

    Thanks for creating the issue @pbtrudel, cc @bas3line pinging you for rc.

  3. bas3line commented on Oct 9, 2026

    @bas3line
    Member

    Hi, thanks for reporting taking a look at it now

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions