[Bug]: ⚠️ Claude provider: every turn gets stuck showing "Working..." forever after the CLI already completed — stop button doesn't recover it #4452

Description

@nikolas-nelson

Before submitting

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

Area

apps/desktop

Steps to reproduce

Reproduction

  1. Corporate network with an HTTPS proxy in front of all outbound traffic (relevant — this manifests very consistently in this kind of environment, may not repro on unrestricted networks).
  2. Open T3 Code, start a brand-new thread, model = Claude (Bedrock), send any message (e.g. "hi").
  3. The assistant response renders correctly.
  4. The "Working for Xs" indicator never clears. Clicking Stop repeatedly does nothing.
  5. Confirmed via local state.sqlite: projection_thread_sessions.status stays running with a stale active_turn_id even though the provider log shows claude/result/success / terminal_reason:completed for that turn.

Expected behavior

After the Claude provider CLI reports a completed turn (claude/result/success, terminal_reason: completed),
the thread session should transition back to ready and accept new messages/turns.

Actual behavior:
The CLI subprocess completes and streams the correct assistant response, but T3 never emits a turn.completed
domain event for it. The thread session stays status: running with a stale active_turn_id forever. The UI
shows "Working for Xs" indefinitely, the composer stays locked, and clicking Stop only records an interrupt
request — it does not recover the session. This happens on ~100% of turns in my environment (reproduces even on
a one-word "hi" with no tools involved). Confirmed via the local state.sqlite (projection_thread_sessions
never gets a status:"ready" follow-up event) and per-thread provider logs
(~/.t3/userdata/logs/provider/<threadId>.log), which show the CLI reporting success while T3's server-side
event log shows nothing after thread.activity-appended.

Likely cause (from apps/server/dist/bin.mjs in app.asar): ClaudeAdapter's completeTurn() awaits
context.query.getContextUsage() (from @anthropic-ai/claude-agent-sdk) before emitting turn.completed.
That SDK call has no timeout on its underlying control-request Promise. In my environment (corporate network,
all traffic through an internal HTTPS proxy, Claude via AWS Bedrock) this call appears to hang, which blocks
turn.completed from ever being emitted — even though the CLI already finished and streamed its answer.

Actual behavior

Environment

  • T3 Code (Nightly) 0.0.29-nightly.20260724.892
  • macOS (Intel), corporate network with an internal HTTPS proxy in front of all outbound traffic (AWS Bedrock via corporate proxy, not direct Anthropic API)
  • Provider: Claude Agent (claudeAgent adapter), models eu.anthropic.claude-sonnet-5 / eu.anthropic.claude-opus-4-8 via AWS Bedrock
  • claude CLI 2.1.218, invoked by T3 with --setting-sources=user,project,local

Summary

Every single turn, even a trivial one-word prompt like "hi", gets stuck showing "Working for Xm Ys" indefinitely in the UI. Clicking Stop does not recover it — it only records a thread.turn-interrupt-requested domain event but the session never actually returns to ready. This happens on every new thread/session, 100% reproducible, not an occasional race.

Root cause (confirmed from logs + DB + app.asar source)

  1. The claude CLI subprocess spawned by T3 does complete successfully. The per-thread provider log (~/.t3/userdata/logs/provider/<threadId>.log) always shows a clean:
    claude/result/success ... "terminal_reason":"completed" ... "result":"Ahoj! 👋\n\nJak ti můžu dnes pomoct?"
    
  2. T3's server never turns this into a turn.completed domain event. Querying the local event store confirms it:
    -- ~/.t3/userdata/state.sqliteSELECT event_type FROM orchestration_events WHERE stream_id='<threadId>'ORDER BY sequence;
    -- thread.created, thread.message-sent, thread.turn-start-requested,-- thread.session-set (status:starting), thread.session-set (status:running),-- thread.message-sent (assistant text), thread.activity-appended-- ... and then NOTHING. No thread.session-set with status:"ready" ever follows.
  3. projection_thread_sessions for the thread stays status='running' with active_turn_id pointing at the turn that the CLI already finished, forever (until the idle reaper eventually kills it ~30 min later, or the user restarts the app — which does not fix it either, since it's persisted in state.sqlite).
  4. Clicking Stop repeatedly only emits thread.turn-interrupt-requested (confirmed via orchestration.command.thread.turn.interrupt spans in server.trace.ndjson) — it never flips the session back to ready on a turn the provider has already completed and exited.

Where it appears to break (apps/server/dist/bin.mjs, extracted from app.asar)

In the Claude adapter's completeTurn() (called from handleResultMessage when a result SDK message with subtype:"success" arrives):

constcompleteTurn=Effect.fn("completeTurn")(function*(context,status,errorMessage,result){
...
constcontextUsageSnapshot=yield*queryCurrentContextUsage(context, ...);
...
yield*offerRuntimeEvent({type: "turn.completed", ... });// <-- never reached
...
});constqueryCurrentContextUsage=Effect.fn("queryCurrentContextUsage")(function*(context,totalProcessedTokens){if(!context.query.getContextUsage)return;constusage=yield*Effect.promise(async()=>{try{returnawaitcontext.query.getContextUsage?.();}catch{return;}});
...
});

context.query.getContextUsage() comes from @anthropic-ai/claude-agent-sdk's Query.getContextUsage():

asyncgetContextUsage(){return(awaitthis.request({subtype:"get_context_usage"})).response}

which goes through the SDK's internal request() control-protocol call:

request(e){lett=Math.random().toString(36).substring(2,15);
...
returnnewPromise((o,i)=>{this.pendingControlResponses.set(t,{handler: ...,reject: i});Promise.resolve(this.transport.write(...)).catch((s)=>{this.pendingControlResponses.delete(t);i(s)});});}

This Promise has no timeout. If the CLI subprocess never sends back a control_response for the get_context_usage control request (e.g. because it's blocked on an internal network call under a corporate proxy, or any other stall), the await in queryCurrentContextUsage hangs forever. Since completeTurn() awaits this before emitting turn.completed, the whole turn-completion pipeline silently deadlocks even though the CLI already streamed the final result message and the user-visible answer.

This would explain why:

  • The visible chat response ("Hi! 👋 ....") does show up (it comes from the earlier assistant message handling, before completeTurn runs).
  • The turn never settles to ready, so the UI shows "Working..." forever.
  • Stop does nothing useful — interrupting a turn whose SDK query object is itself hung on an unrelated internal request doesn't unstick that hung await.
  • This reproduces on literally every message ("hi" included), not just tool-heavy turns — consistent with a stall in a fixed post-turn step (getContextUsage) that runs on every single turn completion, not with something related to turn content.

I don't have 100% certainty this exact control request is what's hanging (I was not able to attach a debugger to the running claude CLI process to confirm), but the DB/log evidence rules out everything before completeTurn(), and the code path is the only awaited, unbounded, non-timed-out promise between "CLI reports success" and "turn.completed is emitted."

Suggested fix

  • Add a timeout (e.g. 5–10s) around context.query.getContextUsage() in queryCurrentContextUsage (apps/server ClaudeAdapter), falling back to the last known usage snapshot on timeout — the existing try/catch only handles rejections, not hangs.
  • More generally, any await on an SDK control-request Promise inside completeTurn() should be wrapped in a race against a timeout, since a stuck one currently blocks the entire terminal lifecycle event for that turn.

Impact

Blocks work completely

Version or commit

T3 Code (Nightly) 0.0.29-nightly.20260724.892

Environment

No response

Logs or stack traces

Screenshots, recordings, or supporting files

No response

Workaround

Manually patching the local DB (~/.t3/userdata/state.sqlite, table projection_thread_sessions) — setting status='ready', active_turn_id=NULL for the affected thread — unsticks that specific thread, but the same thing recurs on the very next new session, since the root cause is a per-turn stall, not corrupted state.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething is broken or behaving incorrectly.needs-triageIssue needs maintainer review and initial categorization.

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions

      , 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
       blocks
      (function() {
      function addCopyButtons() {
      document.querySelectorAll('pre code').forEach(function(codeBlock) {
      if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
      codeBlock.parentElement.setAttribute('data-copy-added', 'true');
      var btn = document.createElement('button');
      btn.textContent = 'Copy';
      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;';
      btn.onmouseover = function() { this.style.opacity = '1'; };
      btn.onmouseout = function() { this.style.opacity = '0.7'; };
      btn.onclick = function() {
      navigator.clipboard.writeText(codeBlock.textContent).then(function() {
      btn.textContent = 'Copied!';
      setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
      });
      };
      codeBlock.parentElement.style.position = 'relative';
      codeBlock.parentElement.appendChild(btn);
      });
      }
      addCopyButtons();
      // Re-run on dynamic content
      var observer = new MutationObserver(addCopyButtons);
      observer.observe(document.body, { childList: true, subtree: true });
      })();
      }
      } 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

      [Bug]: ⚠️ Claude provider: every turn gets stuck showing "Working..." forever after the CLI already completed — stop button doesn't recover it #4452

      Description

      @nikolas-nelson

      Before submitting

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

      Area

      apps/desktop

      Steps to reproduce

      Reproduction

      1. Corporate network with an HTTPS proxy in front of all outbound traffic (relevant — this manifests very consistently in this kind of environment, may not repro on unrestricted networks).
      2. Open T3 Code, start a brand-new thread, model = Claude (Bedrock), send any message (e.g. "hi").
      3. The assistant response renders correctly.
      4. The "Working for Xs" indicator never clears. Clicking Stop repeatedly does nothing.
      5. Confirmed via local state.sqlite: projection_thread_sessions.status stays running with a stale active_turn_id even though the provider log shows claude/result/success / terminal_reason:completed for that turn.

      Expected behavior

      After the Claude provider CLI reports a completed turn (claude/result/success, terminal_reason: completed),
      the thread session should transition back to ready and accept new messages/turns.

      Actual behavior:
      The CLI subprocess completes and streams the correct assistant response, but T3 never emits a turn.completed
      domain event for it. The thread session stays status: running with a stale active_turn_id forever. The UI
      shows "Working for Xs" indefinitely, the composer stays locked, and clicking Stop only records an interrupt
      request — it does not recover the session. This happens on ~100% of turns in my environment (reproduces even on
      a one-word "hi" with no tools involved). Confirmed via the local state.sqlite (projection_thread_sessions
      never gets a status:"ready" follow-up event) and per-thread provider logs
      (~/.t3/userdata/logs/provider/<threadId>.log), which show the CLI reporting success while T3's server-side
      event log shows nothing after thread.activity-appended.

      Likely cause (from apps/server/dist/bin.mjs in app.asar): ClaudeAdapter's completeTurn() awaits
      context.query.getContextUsage() (from @anthropic-ai/claude-agent-sdk) before emitting turn.completed.
      That SDK call has no timeout on its underlying control-request Promise. In my environment (corporate network,
      all traffic through an internal HTTPS proxy, Claude via AWS Bedrock) this call appears to hang, which blocks
      turn.completed from ever being emitted — even though the CLI already finished and streamed its answer.

      Actual behavior

      Environment

      • T3 Code (Nightly) 0.0.29-nightly.20260724.892
      • macOS (Intel), corporate network with an internal HTTPS proxy in front of all outbound traffic (AWS Bedrock via corporate proxy, not direct Anthropic API)
      • Provider: Claude Agent (claudeAgent adapter), models eu.anthropic.claude-sonnet-5 / eu.anthropic.claude-opus-4-8 via AWS Bedrock
      • claude CLI 2.1.218, invoked by T3 with --setting-sources=user,project,local

      Summary

      Every single turn, even a trivial one-word prompt like "hi", gets stuck showing "Working for Xm Ys" indefinitely in the UI. Clicking Stop does not recover it — it only records a thread.turn-interrupt-requested domain event but the session never actually returns to ready. This happens on every new thread/session, 100% reproducible, not an occasional race.

      Root cause (confirmed from logs + DB + app.asar source)

      1. The claude CLI subprocess spawned by T3 does complete successfully. The per-thread provider log (~/.t3/userdata/logs/provider/<threadId>.log) always shows a clean:
        claude/result/success ... "terminal_reason":"completed" ... "result":"Ahoj! 👋\n\nJak ti můžu dnes pomoct?"
        
      2. T3's server never turns this into a turn.completed domain event. Querying the local event store confirms it:
        -- ~/.t3/userdata/state.sqliteSELECT event_type FROM orchestration_events WHERE stream_id='<threadId>'ORDER BY sequence;
        -- thread.created, thread.message-sent, thread.turn-start-requested,-- thread.session-set (status:starting), thread.session-set (status:running),-- thread.message-sent (assistant text), thread.activity-appended-- ... and then NOTHING. No thread.session-set with status:"ready" ever follows.
      3. projection_thread_sessions for the thread stays status='running' with active_turn_id pointing at the turn that the CLI already finished, forever (until the idle reaper eventually kills it ~30 min later, or the user restarts the app — which does not fix it either, since it's persisted in state.sqlite).
      4. Clicking Stop repeatedly only emits thread.turn-interrupt-requested (confirmed via orchestration.command.thread.turn.interrupt spans in server.trace.ndjson) — it never flips the session back to ready on a turn the provider has already completed and exited.

      Where it appears to break (apps/server/dist/bin.mjs, extracted from app.asar)

      In the Claude adapter's completeTurn() (called from handleResultMessage when a result SDK message with subtype:"success" arrives):

      constcompleteTurn=Effect.fn("completeTurn")(function*(context,status,errorMessage,result){
      ...
      constcontextUsageSnapshot=yield*queryCurrentContextUsage(context, ...);
      ...
      yield*offerRuntimeEvent({type: "turn.completed", ... });// <-- never reached
      ...
      });constqueryCurrentContextUsage=Effect.fn("queryCurrentContextUsage")(function*(context,totalProcessedTokens){if(!context.query.getContextUsage)return;constusage=yield*Effect.promise(async()=>{try{returnawaitcontext.query.getContextUsage?.();}catch{return;}});
      ...
      });

      context.query.getContextUsage() comes from @anthropic-ai/claude-agent-sdk's Query.getContextUsage():

      asyncgetContextUsage(){return(awaitthis.request({subtype:"get_context_usage"})).response}

      which goes through the SDK's internal request() control-protocol call:

      request(e){lett=Math.random().toString(36).substring(2,15);
      ...
      returnnewPromise((o,i)=>{this.pendingControlResponses.set(t,{handler: ...,reject: i});Promise.resolve(this.transport.write(...)).catch((s)=>{this.pendingControlResponses.delete(t);i(s)});});}

      This Promise has no timeout. If the CLI subprocess never sends back a control_response for the get_context_usage control request (e.g. because it's blocked on an internal network call under a corporate proxy, or any other stall), the await in queryCurrentContextUsage hangs forever. Since completeTurn() awaits this before emitting turn.completed, the whole turn-completion pipeline silently deadlocks even though the CLI already streamed the final result message and the user-visible answer.

      This would explain why:

      • The visible chat response ("Hi! 👋 ....") does show up (it comes from the earlier assistant message handling, before completeTurn runs).
      • The turn never settles to ready, so the UI shows "Working..." forever.
      • Stop does nothing useful — interrupting a turn whose SDK query object is itself hung on an unrelated internal request doesn't unstick that hung await.
      • This reproduces on literally every message ("hi" included), not just tool-heavy turns — consistent with a stall in a fixed post-turn step (getContextUsage) that runs on every single turn completion, not with something related to turn content.

      I don't have 100% certainty this exact control request is what's hanging (I was not able to attach a debugger to the running claude CLI process to confirm), but the DB/log evidence rules out everything before completeTurn(), and the code path is the only awaited, unbounded, non-timed-out promise between "CLI reports success" and "turn.completed is emitted."

      Suggested fix

      • Add a timeout (e.g. 5–10s) around context.query.getContextUsage() in queryCurrentContextUsage (apps/server ClaudeAdapter), falling back to the last known usage snapshot on timeout — the existing try/catch only handles rejections, not hangs.
      • More generally, any await on an SDK control-request Promise inside completeTurn() should be wrapped in a race against a timeout, since a stuck one currently blocks the entire terminal lifecycle event for that turn.

      Impact

      Blocks work completely

      Version or commit

      T3 Code (Nightly) 0.0.29-nightly.20260724.892

      Environment

      No response

      Logs or stack traces

      Screenshots, recordings, or supporting files

      No response

      Workaround

      Manually patching the local DB (~/.t3/userdata/state.sqlite, table projection_thread_sessions) — setting status='ready', active_turn_id=NULL for the affected thread — unsticks that specific thread, but the same thing recurs on the very next new session, since the root cause is a per-turn stall, not corrupted state.

      Metadata

      Metadata

      Assignees

      No one assigned

        Labels

        bugSomething is broken or behaving incorrectly.needs-triageIssue needs maintainer review and initial categorization.

        Type

        No type

        Projects

        No projects

          Milestone

          No milestone

          Relationships

          None yet

          Development

          No branches or pull requests

          Issue actions

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

          [Bug]: ⚠️ Claude provider: every turn gets stuck showing "Working..." forever after the CLI already completed — stop button doesn't recover it #4452

          Description

          @nikolas-nelson

          Before submitting

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

          Area

          apps/desktop

          Steps to reproduce

          Reproduction

          1. Corporate network with an HTTPS proxy in front of all outbound traffic (relevant — this manifests very consistently in this kind of environment, may not repro on unrestricted networks).
          2. Open T3 Code, start a brand-new thread, model = Claude (Bedrock), send any message (e.g. "hi").
          3. The assistant response renders correctly.
          4. The "Working for Xs" indicator never clears. Clicking Stop repeatedly does nothing.
          5. Confirmed via local state.sqlite: projection_thread_sessions.status stays running with a stale active_turn_id even though the provider log shows claude/result/success / terminal_reason:completed for that turn.

          Expected behavior

          After the Claude provider CLI reports a completed turn (claude/result/success, terminal_reason: completed),
          the thread session should transition back to ready and accept new messages/turns.

          Actual behavior:
          The CLI subprocess completes and streams the correct assistant response, but T3 never emits a turn.completed
          domain event for it. The thread session stays status: running with a stale active_turn_id forever. The UI
          shows "Working for Xs" indefinitely, the composer stays locked, and clicking Stop only records an interrupt
          request — it does not recover the session. This happens on ~100% of turns in my environment (reproduces even on
          a one-word "hi" with no tools involved). Confirmed via the local state.sqlite (projection_thread_sessions
          never gets a status:"ready" follow-up event) and per-thread provider logs
          (~/.t3/userdata/logs/provider/<threadId>.log), which show the CLI reporting success while T3's server-side
          event log shows nothing after thread.activity-appended.

          Likely cause (from apps/server/dist/bin.mjs in app.asar): ClaudeAdapter's completeTurn() awaits
          context.query.getContextUsage() (from @anthropic-ai/claude-agent-sdk) before emitting turn.completed.
          That SDK call has no timeout on its underlying control-request Promise. In my environment (corporate network,
          all traffic through an internal HTTPS proxy, Claude via AWS Bedrock) this call appears to hang, which blocks
          turn.completed from ever being emitted — even though the CLI already finished and streamed its answer.

          Actual behavior

          Environment

          • T3 Code (Nightly) 0.0.29-nightly.20260724.892
          • macOS (Intel), corporate network with an internal HTTPS proxy in front of all outbound traffic (AWS Bedrock via corporate proxy, not direct Anthropic API)
          • Provider: Claude Agent (claudeAgent adapter), models eu.anthropic.claude-sonnet-5 / eu.anthropic.claude-opus-4-8 via AWS Bedrock
          • claude CLI 2.1.218, invoked by T3 with --setting-sources=user,project,local

          Summary

          Every single turn, even a trivial one-word prompt like "hi", gets stuck showing "Working for Xm Ys" indefinitely in the UI. Clicking Stop does not recover it — it only records a thread.turn-interrupt-requested domain event but the session never actually returns to ready. This happens on every new thread/session, 100% reproducible, not an occasional race.

          Root cause (confirmed from logs + DB + app.asar source)

          1. The claude CLI subprocess spawned by T3 does complete successfully. The per-thread provider log (~/.t3/userdata/logs/provider/<threadId>.log) always shows a clean:
            claude/result/success ... "terminal_reason":"completed" ... "result":"Ahoj! 👋\n\nJak ti můžu dnes pomoct?"
            
          2. T3's server never turns this into a turn.completed domain event. Querying the local event store confirms it:
            -- ~/.t3/userdata/state.sqliteSELECT event_type FROM orchestration_events WHERE stream_id='<threadId>'ORDER BY sequence;
            -- thread.created, thread.message-sent, thread.turn-start-requested,-- thread.session-set (status:starting), thread.session-set (status:running),-- thread.message-sent (assistant text), thread.activity-appended-- ... and then NOTHING. No thread.session-set with status:"ready" ever follows.
          3. projection_thread_sessions for the thread stays status='running' with active_turn_id pointing at the turn that the CLI already finished, forever (until the idle reaper eventually kills it ~30 min later, or the user restarts the app — which does not fix it either, since it's persisted in state.sqlite).
          4. Clicking Stop repeatedly only emits thread.turn-interrupt-requested (confirmed via orchestration.command.thread.turn.interrupt spans in server.trace.ndjson) — it never flips the session back to ready on a turn the provider has already completed and exited.

          Where it appears to break (apps/server/dist/bin.mjs, extracted from app.asar)

          In the Claude adapter's completeTurn() (called from handleResultMessage when a result SDK message with subtype:"success" arrives):

          constcompleteTurn=Effect.fn("completeTurn")(function*(context,status,errorMessage,result){
          ...
          constcontextUsageSnapshot=yield*queryCurrentContextUsage(context, ...);
          ...
          yield*offerRuntimeEvent({type: "turn.completed", ... });// <-- never reached
          ...
          });constqueryCurrentContextUsage=Effect.fn("queryCurrentContextUsage")(function*(context,totalProcessedTokens){if(!context.query.getContextUsage)return;constusage=yield*Effect.promise(async()=>{try{returnawaitcontext.query.getContextUsage?.();}catch{return;}});
          ...
          });

          context.query.getContextUsage() comes from @anthropic-ai/claude-agent-sdk's Query.getContextUsage():

          asyncgetContextUsage(){return(awaitthis.request({subtype:"get_context_usage"})).response}

          which goes through the SDK's internal request() control-protocol call:

          request(e){lett=Math.random().toString(36).substring(2,15);
          ...
          returnnewPromise((o,i)=>{this.pendingControlResponses.set(t,{handler: ...,reject: i});Promise.resolve(this.transport.write(...)).catch((s)=>{this.pendingControlResponses.delete(t);i(s)});});}

          This Promise has no timeout. If the CLI subprocess never sends back a control_response for the get_context_usage control request (e.g. because it's blocked on an internal network call under a corporate proxy, or any other stall), the await in queryCurrentContextUsage hangs forever. Since completeTurn() awaits this before emitting turn.completed, the whole turn-completion pipeline silently deadlocks even though the CLI already streamed the final result message and the user-visible answer.

          This would explain why:

          • The visible chat response ("Hi! 👋 ....") does show up (it comes from the earlier assistant message handling, before completeTurn runs).
          • The turn never settles to ready, so the UI shows "Working..." forever.
          • Stop does nothing useful — interrupting a turn whose SDK query object is itself hung on an unrelated internal request doesn't unstick that hung await.
          • This reproduces on literally every message ("hi" included), not just tool-heavy turns — consistent with a stall in a fixed post-turn step (getContextUsage) that runs on every single turn completion, not with something related to turn content.

          I don't have 100% certainty this exact control request is what's hanging (I was not able to attach a debugger to the running claude CLI process to confirm), but the DB/log evidence rules out everything before completeTurn(), and the code path is the only awaited, unbounded, non-timed-out promise between "CLI reports success" and "turn.completed is emitted."

          Suggested fix

          • Add a timeout (e.g. 5–10s) around context.query.getContextUsage() in queryCurrentContextUsage (apps/server ClaudeAdapter), falling back to the last known usage snapshot on timeout — the existing try/catch only handles rejections, not hangs.
          • More generally, any await on an SDK control-request Promise inside completeTurn() should be wrapped in a race against a timeout, since a stuck one currently blocks the entire terminal lifecycle event for that turn.

          Impact

          Blocks work completely

          Version or commit

          T3 Code (Nightly) 0.0.29-nightly.20260724.892

          Environment

          No response

          Logs or stack traces

          Screenshots, recordings, or supporting files

          No response

          Workaround

          Manually patching the local DB (~/.t3/userdata/state.sqlite, table projection_thread_sessions) — setting status='ready', active_turn_id=NULL for the affected thread — unsticks that specific thread, but the same thing recurs on the very next new session, since the root cause is a per-turn stall, not corrupted state.

          Metadata

          Metadata

          Assignees

          No one assigned

            Labels

            bugSomething is broken or behaving incorrectly.needs-triageIssue needs maintainer review and initial categorization.

            Type

            No type

            Projects

            No projects

              Milestone

              No milestone

              Relationships

              None yet

              Development

              No branches or pull requests

              Issue actions

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

              [Bug]: ⚠️ Claude provider: every turn gets stuck showing "Working..." forever after the CLI already completed — stop button doesn't recover it #4452

              Description

              @nikolas-nelson

              Before submitting

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

              Area

              apps/desktop

              Steps to reproduce

              Reproduction

              1. Corporate network with an HTTPS proxy in front of all outbound traffic (relevant — this manifests very consistently in this kind of environment, may not repro on unrestricted networks).
              2. Open T3 Code, start a brand-new thread, model = Claude (Bedrock), send any message (e.g. "hi").
              3. The assistant response renders correctly.
              4. The "Working for Xs" indicator never clears. Clicking Stop repeatedly does nothing.
              5. Confirmed via local state.sqlite: projection_thread_sessions.status stays running with a stale active_turn_id even though the provider log shows claude/result/success / terminal_reason:completed for that turn.

              Expected behavior

              After the Claude provider CLI reports a completed turn (claude/result/success, terminal_reason: completed),
              the thread session should transition back to ready and accept new messages/turns.

              Actual behavior:
              The CLI subprocess completes and streams the correct assistant response, but T3 never emits a turn.completed
              domain event for it. The thread session stays status: running with a stale active_turn_id forever. The UI
              shows "Working for Xs" indefinitely, the composer stays locked, and clicking Stop only records an interrupt
              request — it does not recover the session. This happens on ~100% of turns in my environment (reproduces even on
              a one-word "hi" with no tools involved). Confirmed via the local state.sqlite (projection_thread_sessions
              never gets a status:"ready" follow-up event) and per-thread provider logs
              (~/.t3/userdata/logs/provider/<threadId>.log), which show the CLI reporting success while T3's server-side
              event log shows nothing after thread.activity-appended.

              Likely cause (from apps/server/dist/bin.mjs in app.asar): ClaudeAdapter's completeTurn() awaits
              context.query.getContextUsage() (from @anthropic-ai/claude-agent-sdk) before emitting turn.completed.
              That SDK call has no timeout on its underlying control-request Promise. In my environment (corporate network,
              all traffic through an internal HTTPS proxy, Claude via AWS Bedrock) this call appears to hang, which blocks
              turn.completed from ever being emitted — even though the CLI already finished and streamed its answer.

              Actual behavior

              Environment

              • T3 Code (Nightly) 0.0.29-nightly.20260724.892
              • macOS (Intel), corporate network with an internal HTTPS proxy in front of all outbound traffic (AWS Bedrock via corporate proxy, not direct Anthropic API)
              • Provider: Claude Agent (claudeAgent adapter), models eu.anthropic.claude-sonnet-5 / eu.anthropic.claude-opus-4-8 via AWS Bedrock
              • claude CLI 2.1.218, invoked by T3 with --setting-sources=user,project,local

              Summary

              Every single turn, even a trivial one-word prompt like "hi", gets stuck showing "Working for Xm Ys" indefinitely in the UI. Clicking Stop does not recover it — it only records a thread.turn-interrupt-requested domain event but the session never actually returns to ready. This happens on every new thread/session, 100% reproducible, not an occasional race.

              Root cause (confirmed from logs + DB + app.asar source)

              1. The claude CLI subprocess spawned by T3 does complete successfully. The per-thread provider log (~/.t3/userdata/logs/provider/<threadId>.log) always shows a clean:
                claude/result/success ... "terminal_reason":"completed" ... "result":"Ahoj! 👋\n\nJak ti můžu dnes pomoct?"
                
              2. T3's server never turns this into a turn.completed domain event. Querying the local event store confirms it:
                -- ~/.t3/userdata/state.sqliteSELECT event_type FROM orchestration_events WHERE stream_id='<threadId>'ORDER BY sequence;
                -- thread.created, thread.message-sent, thread.turn-start-requested,-- thread.session-set (status:starting), thread.session-set (status:running),-- thread.message-sent (assistant text), thread.activity-appended-- ... and then NOTHING. No thread.session-set with status:"ready" ever follows.
              3. projection_thread_sessions for the thread stays status='running' with active_turn_id pointing at the turn that the CLI already finished, forever (until the idle reaper eventually kills it ~30 min later, or the user restarts the app — which does not fix it either, since it's persisted in state.sqlite).
              4. Clicking Stop repeatedly only emits thread.turn-interrupt-requested (confirmed via orchestration.command.thread.turn.interrupt spans in server.trace.ndjson) — it never flips the session back to ready on a turn the provider has already completed and exited.

              Where it appears to break (apps/server/dist/bin.mjs, extracted from app.asar)

              In the Claude adapter's completeTurn() (called from handleResultMessage when a result SDK message with subtype:"success" arrives):

              constcompleteTurn=Effect.fn("completeTurn")(function*(context,status,errorMessage,result){
              ...
              constcontextUsageSnapshot=yield*queryCurrentContextUsage(context, ...);
              ...
              yield*offerRuntimeEvent({type: "turn.completed", ... });// <-- never reached
              ...
              });constqueryCurrentContextUsage=Effect.fn("queryCurrentContextUsage")(function*(context,totalProcessedTokens){if(!context.query.getContextUsage)return;constusage=yield*Effect.promise(async()=>{try{returnawaitcontext.query.getContextUsage?.();}catch{return;}});
              ...
              });

              context.query.getContextUsage() comes from @anthropic-ai/claude-agent-sdk's Query.getContextUsage():

              asyncgetContextUsage(){return(awaitthis.request({subtype:"get_context_usage"})).response}

              which goes through the SDK's internal request() control-protocol call:

              request(e){lett=Math.random().toString(36).substring(2,15);
              ...
              returnnewPromise((o,i)=>{this.pendingControlResponses.set(t,{handler: ...,reject: i});Promise.resolve(this.transport.write(...)).catch((s)=>{this.pendingControlResponses.delete(t);i(s)});});}

              This Promise has no timeout. If the CLI subprocess never sends back a control_response for the get_context_usage control request (e.g. because it's blocked on an internal network call under a corporate proxy, or any other stall), the await in queryCurrentContextUsage hangs forever. Since completeTurn() awaits this before emitting turn.completed, the whole turn-completion pipeline silently deadlocks even though the CLI already streamed the final result message and the user-visible answer.

              This would explain why:

              • The visible chat response ("Hi! 👋 ....") does show up (it comes from the earlier assistant message handling, before completeTurn runs).
              • The turn never settles to ready, so the UI shows "Working..." forever.
              • Stop does nothing useful — interrupting a turn whose SDK query object is itself hung on an unrelated internal request doesn't unstick that hung await.
              • This reproduces on literally every message ("hi" included), not just tool-heavy turns — consistent with a stall in a fixed post-turn step (getContextUsage) that runs on every single turn completion, not with something related to turn content.

              I don't have 100% certainty this exact control request is what's hanging (I was not able to attach a debugger to the running claude CLI process to confirm), but the DB/log evidence rules out everything before completeTurn(), and the code path is the only awaited, unbounded, non-timed-out promise between "CLI reports success" and "turn.completed is emitted."

              Suggested fix

              • Add a timeout (e.g. 5–10s) around context.query.getContextUsage() in queryCurrentContextUsage (apps/server ClaudeAdapter), falling back to the last known usage snapshot on timeout — the existing try/catch only handles rejections, not hangs.
              • More generally, any await on an SDK control-request Promise inside completeTurn() should be wrapped in a race against a timeout, since a stuck one currently blocks the entire terminal lifecycle event for that turn.

              Impact

              Blocks work completely

              Version or commit

              T3 Code (Nightly) 0.0.29-nightly.20260724.892

              Environment

              No response

              Logs or stack traces

              Screenshots, recordings, or supporting files

              No response

              Workaround

              Manually patching the local DB (~/.t3/userdata/state.sqlite, table projection_thread_sessions) — setting status='ready', active_turn_id=NULL for the affected thread — unsticks that specific thread, but the same thing recurs on the very next new session, since the root cause is a per-turn stall, not corrupted state.

              Metadata

              Metadata

              Assignees

              No one assigned

                Labels

                bugSomething is broken or behaving incorrectly.needs-triageIssue needs maintainer review and initial categorization.

                Type

                No type

                Projects

                No projects

                  Milestone

                  No milestone

                  Relationships

                  None yet

                  Development

                  No branches or pull requests

                  Issue actions

                  , 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } 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

                  [Bug]: ⚠️ Claude provider: every turn gets stuck showing "Working..." forever after the CLI already completed — stop button doesn't recover it #4452

                  Description

                  @nikolas-nelson

                  Before submitting

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

                  Area

                  apps/desktop

                  Steps to reproduce

                  Reproduction

                  1. Corporate network with an HTTPS proxy in front of all outbound traffic (relevant — this manifests very consistently in this kind of environment, may not repro on unrestricted networks).
                  2. Open T3 Code, start a brand-new thread, model = Claude (Bedrock), send any message (e.g. "hi").
                  3. The assistant response renders correctly.
                  4. The "Working for Xs" indicator never clears. Clicking Stop repeatedly does nothing.
                  5. Confirmed via local state.sqlite: projection_thread_sessions.status stays running with a stale active_turn_id even though the provider log shows claude/result/success / terminal_reason:completed for that turn.

                  Expected behavior

                  After the Claude provider CLI reports a completed turn (claude/result/success, terminal_reason: completed),
                  the thread session should transition back to ready and accept new messages/turns.

                  Actual behavior:
                  The CLI subprocess completes and streams the correct assistant response, but T3 never emits a turn.completed
                  domain event for it. The thread session stays status: running with a stale active_turn_id forever. The UI
                  shows "Working for Xs" indefinitely, the composer stays locked, and clicking Stop only records an interrupt
                  request — it does not recover the session. This happens on ~100% of turns in my environment (reproduces even on
                  a one-word "hi" with no tools involved). Confirmed via the local state.sqlite (projection_thread_sessions
                  never gets a status:"ready" follow-up event) and per-thread provider logs
                  (~/.t3/userdata/logs/provider/<threadId>.log), which show the CLI reporting success while T3's server-side
                  event log shows nothing after thread.activity-appended.

                  Likely cause (from apps/server/dist/bin.mjs in app.asar): ClaudeAdapter's completeTurn() awaits
                  context.query.getContextUsage() (from @anthropic-ai/claude-agent-sdk) before emitting turn.completed.
                  That SDK call has no timeout on its underlying control-request Promise. In my environment (corporate network,
                  all traffic through an internal HTTPS proxy, Claude via AWS Bedrock) this call appears to hang, which blocks
                  turn.completed from ever being emitted — even though the CLI already finished and streamed its answer.

                  Actual behavior

                  Environment

                  • T3 Code (Nightly) 0.0.29-nightly.20260724.892
                  • macOS (Intel), corporate network with an internal HTTPS proxy in front of all outbound traffic (AWS Bedrock via corporate proxy, not direct Anthropic API)
                  • Provider: Claude Agent (claudeAgent adapter), models eu.anthropic.claude-sonnet-5 / eu.anthropic.claude-opus-4-8 via AWS Bedrock
                  • claude CLI 2.1.218, invoked by T3 with --setting-sources=user,project,local

                  Summary

                  Every single turn, even a trivial one-word prompt like "hi", gets stuck showing "Working for Xm Ys" indefinitely in the UI. Clicking Stop does not recover it — it only records a thread.turn-interrupt-requested domain event but the session never actually returns to ready. This happens on every new thread/session, 100% reproducible, not an occasional race.

                  Root cause (confirmed from logs + DB + app.asar source)

                  1. The claude CLI subprocess spawned by T3 does complete successfully. The per-thread provider log (~/.t3/userdata/logs/provider/<threadId>.log) always shows a clean:
                    claude/result/success ... "terminal_reason":"completed" ... "result":"Ahoj! 👋\n\nJak ti můžu dnes pomoct?"
                    
                  2. T3's server never turns this into a turn.completed domain event. Querying the local event store confirms it:
                    -- ~/.t3/userdata/state.sqliteSELECT event_type FROM orchestration_events WHERE stream_id='<threadId>'ORDER BY sequence;
                    -- thread.created, thread.message-sent, thread.turn-start-requested,-- thread.session-set (status:starting), thread.session-set (status:running),-- thread.message-sent (assistant text), thread.activity-appended-- ... and then NOTHING. No thread.session-set with status:"ready" ever follows.
                  3. projection_thread_sessions for the thread stays status='running' with active_turn_id pointing at the turn that the CLI already finished, forever (until the idle reaper eventually kills it ~30 min later, or the user restarts the app — which does not fix it either, since it's persisted in state.sqlite).
                  4. Clicking Stop repeatedly only emits thread.turn-interrupt-requested (confirmed via orchestration.command.thread.turn.interrupt spans in server.trace.ndjson) — it never flips the session back to ready on a turn the provider has already completed and exited.

                  Where it appears to break (apps/server/dist/bin.mjs, extracted from app.asar)

                  In the Claude adapter's completeTurn() (called from handleResultMessage when a result SDK message with subtype:"success" arrives):

                  constcompleteTurn=Effect.fn("completeTurn")(function*(context,status,errorMessage,result){
                  ...
                  constcontextUsageSnapshot=yield*queryCurrentContextUsage(context, ...);
                  ...
                  yield*offerRuntimeEvent({type: "turn.completed", ... });// <-- never reached
                  ...
                  });constqueryCurrentContextUsage=Effect.fn("queryCurrentContextUsage")(function*(context,totalProcessedTokens){if(!context.query.getContextUsage)return;constusage=yield*Effect.promise(async()=>{try{returnawaitcontext.query.getContextUsage?.();}catch{return;}});
                  ...
                  });

                  context.query.getContextUsage() comes from @anthropic-ai/claude-agent-sdk's Query.getContextUsage():

                  asyncgetContextUsage(){return(awaitthis.request({subtype:"get_context_usage"})).response}

                  which goes through the SDK's internal request() control-protocol call:

                  request(e){lett=Math.random().toString(36).substring(2,15);
                  ...
                  returnnewPromise((o,i)=>{this.pendingControlResponses.set(t,{handler: ...,reject: i});Promise.resolve(this.transport.write(...)).catch((s)=>{this.pendingControlResponses.delete(t);i(s)});});}

                  This Promise has no timeout. If the CLI subprocess never sends back a control_response for the get_context_usage control request (e.g. because it's blocked on an internal network call under a corporate proxy, or any other stall), the await in queryCurrentContextUsage hangs forever. Since completeTurn() awaits this before emitting turn.completed, the whole turn-completion pipeline silently deadlocks even though the CLI already streamed the final result message and the user-visible answer.

                  This would explain why:

                  • The visible chat response ("Hi! 👋 ....") does show up (it comes from the earlier assistant message handling, before completeTurn runs).
                  • The turn never settles to ready, so the UI shows "Working..." forever.
                  • Stop does nothing useful — interrupting a turn whose SDK query object is itself hung on an unrelated internal request doesn't unstick that hung await.
                  • This reproduces on literally every message ("hi" included), not just tool-heavy turns — consistent with a stall in a fixed post-turn step (getContextUsage) that runs on every single turn completion, not with something related to turn content.

                  I don't have 100% certainty this exact control request is what's hanging (I was not able to attach a debugger to the running claude CLI process to confirm), but the DB/log evidence rules out everything before completeTurn(), and the code path is the only awaited, unbounded, non-timed-out promise between "CLI reports success" and "turn.completed is emitted."

                  Suggested fix

                  • Add a timeout (e.g. 5–10s) around context.query.getContextUsage() in queryCurrentContextUsage (apps/server ClaudeAdapter), falling back to the last known usage snapshot on timeout — the existing try/catch only handles rejections, not hangs.
                  • More generally, any await on an SDK control-request Promise inside completeTurn() should be wrapped in a race against a timeout, since a stuck one currently blocks the entire terminal lifecycle event for that turn.

                  Impact

                  Blocks work completely

                  Version or commit

                  T3 Code (Nightly) 0.0.29-nightly.20260724.892

                  Environment

                  No response

                  Logs or stack traces

                  Screenshots, recordings, or supporting files

                  No response

                  Workaround

                  Manually patching the local DB (~/.t3/userdata/state.sqlite, table projection_thread_sessions) — setting status='ready', active_turn_id=NULL for the affected thread — unsticks that specific thread, but the same thing recurs on the very next new session, since the root cause is a per-turn stall, not corrupted state.

                  Metadata

                  Metadata

                  Assignees

                  No one assigned

                    Labels

                    bugSomething is broken or behaving incorrectly.needs-triageIssue needs maintainer review and initial categorization.

                    Type

                    No type

                    Projects

                    No projects

                      Milestone

                      No milestone

                      Relationships

                      None yet

                      Development

                      No branches or pull requests

                      Issue actions

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

                      [Bug]: ⚠️ Claude provider: every turn gets stuck showing "Working..." forever after the CLI already completed — stop button doesn't recover it #4452

                      Description

                      @nikolas-nelson

                      Before submitting

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

                      Area

                      apps/desktop

                      Steps to reproduce

                      Reproduction

                      1. Corporate network with an HTTPS proxy in front of all outbound traffic (relevant — this manifests very consistently in this kind of environment, may not repro on unrestricted networks).
                      2. Open T3 Code, start a brand-new thread, model = Claude (Bedrock), send any message (e.g. "hi").
                      3. The assistant response renders correctly.
                      4. The "Working for Xs" indicator never clears. Clicking Stop repeatedly does nothing.
                      5. Confirmed via local state.sqlite: projection_thread_sessions.status stays running with a stale active_turn_id even though the provider log shows claude/result/success / terminal_reason:completed for that turn.

                      Expected behavior

                      After the Claude provider CLI reports a completed turn (claude/result/success, terminal_reason: completed),
                      the thread session should transition back to ready and accept new messages/turns.

                      Actual behavior:
                      The CLI subprocess completes and streams the correct assistant response, but T3 never emits a turn.completed
                      domain event for it. The thread session stays status: running with a stale active_turn_id forever. The UI
                      shows "Working for Xs" indefinitely, the composer stays locked, and clicking Stop only records an interrupt
                      request — it does not recover the session. This happens on ~100% of turns in my environment (reproduces even on
                      a one-word "hi" with no tools involved). Confirmed via the local state.sqlite (projection_thread_sessions
                      never gets a status:"ready" follow-up event) and per-thread provider logs
                      (~/.t3/userdata/logs/provider/<threadId>.log), which show the CLI reporting success while T3's server-side
                      event log shows nothing after thread.activity-appended.

                      Likely cause (from apps/server/dist/bin.mjs in app.asar): ClaudeAdapter's completeTurn() awaits
                      context.query.getContextUsage() (from @anthropic-ai/claude-agent-sdk) before emitting turn.completed.
                      That SDK call has no timeout on its underlying control-request Promise. In my environment (corporate network,
                      all traffic through an internal HTTPS proxy, Claude via AWS Bedrock) this call appears to hang, which blocks
                      turn.completed from ever being emitted — even though the CLI already finished and streamed its answer.

                      Actual behavior

                      Environment

                      • T3 Code (Nightly) 0.0.29-nightly.20260724.892
                      • macOS (Intel), corporate network with an internal HTTPS proxy in front of all outbound traffic (AWS Bedrock via corporate proxy, not direct Anthropic API)
                      • Provider: Claude Agent (claudeAgent adapter), models eu.anthropic.claude-sonnet-5 / eu.anthropic.claude-opus-4-8 via AWS Bedrock
                      • claude CLI 2.1.218, invoked by T3 with --setting-sources=user,project,local

                      Summary

                      Every single turn, even a trivial one-word prompt like "hi", gets stuck showing "Working for Xm Ys" indefinitely in the UI. Clicking Stop does not recover it — it only records a thread.turn-interrupt-requested domain event but the session never actually returns to ready. This happens on every new thread/session, 100% reproducible, not an occasional race.

                      Root cause (confirmed from logs + DB + app.asar source)

                      1. The claude CLI subprocess spawned by T3 does complete successfully. The per-thread provider log (~/.t3/userdata/logs/provider/<threadId>.log) always shows a clean:
                        claude/result/success ... "terminal_reason":"completed" ... "result":"Ahoj! 👋\n\nJak ti můžu dnes pomoct?"
                        
                      2. T3's server never turns this into a turn.completed domain event. Querying the local event store confirms it:
                        -- ~/.t3/userdata/state.sqliteSELECT event_type FROM orchestration_events WHERE stream_id='<threadId>'ORDER BY sequence;
                        -- thread.created, thread.message-sent, thread.turn-start-requested,-- thread.session-set (status:starting), thread.session-set (status:running),-- thread.message-sent (assistant text), thread.activity-appended-- ... and then NOTHING. No thread.session-set with status:"ready" ever follows.
                      3. projection_thread_sessions for the thread stays status='running' with active_turn_id pointing at the turn that the CLI already finished, forever (until the idle reaper eventually kills it ~30 min later, or the user restarts the app — which does not fix it either, since it's persisted in state.sqlite).
                      4. Clicking Stop repeatedly only emits thread.turn-interrupt-requested (confirmed via orchestration.command.thread.turn.interrupt spans in server.trace.ndjson) — it never flips the session back to ready on a turn the provider has already completed and exited.

                      Where it appears to break (apps/server/dist/bin.mjs, extracted from app.asar)

                      In the Claude adapter's completeTurn() (called from handleResultMessage when a result SDK message with subtype:"success" arrives):

                      constcompleteTurn=Effect.fn("completeTurn")(function*(context,status,errorMessage,result){
                      ...
                      constcontextUsageSnapshot=yield*queryCurrentContextUsage(context, ...);
                      ...
                      yield*offerRuntimeEvent({type: "turn.completed", ... });// <-- never reached
                      ...
                      });constqueryCurrentContextUsage=Effect.fn("queryCurrentContextUsage")(function*(context,totalProcessedTokens){if(!context.query.getContextUsage)return;constusage=yield*Effect.promise(async()=>{try{returnawaitcontext.query.getContextUsage?.();}catch{return;}});
                      ...
                      });

                      context.query.getContextUsage() comes from @anthropic-ai/claude-agent-sdk's Query.getContextUsage():

                      asyncgetContextUsage(){return(awaitthis.request({subtype:"get_context_usage"})).response}

                      which goes through the SDK's internal request() control-protocol call:

                      request(e){lett=Math.random().toString(36).substring(2,15);
                      ...
                      returnnewPromise((o,i)=>{this.pendingControlResponses.set(t,{handler: ...,reject: i});Promise.resolve(this.transport.write(...)).catch((s)=>{this.pendingControlResponses.delete(t);i(s)});});}

                      This Promise has no timeout. If the CLI subprocess never sends back a control_response for the get_context_usage control request (e.g. because it's blocked on an internal network call under a corporate proxy, or any other stall), the await in queryCurrentContextUsage hangs forever. Since completeTurn() awaits this before emitting turn.completed, the whole turn-completion pipeline silently deadlocks even though the CLI already streamed the final result message and the user-visible answer.

                      This would explain why:

                      • The visible chat response ("Hi! 👋 ....") does show up (it comes from the earlier assistant message handling, before completeTurn runs).
                      • The turn never settles to ready, so the UI shows "Working..." forever.
                      • Stop does nothing useful — interrupting a turn whose SDK query object is itself hung on an unrelated internal request doesn't unstick that hung await.
                      • This reproduces on literally every message ("hi" included), not just tool-heavy turns — consistent with a stall in a fixed post-turn step (getContextUsage) that runs on every single turn completion, not with something related to turn content.

                      I don't have 100% certainty this exact control request is what's hanging (I was not able to attach a debugger to the running claude CLI process to confirm), but the DB/log evidence rules out everything before completeTurn(), and the code path is the only awaited, unbounded, non-timed-out promise between "CLI reports success" and "turn.completed is emitted."

                      Suggested fix

                      • Add a timeout (e.g. 5–10s) around context.query.getContextUsage() in queryCurrentContextUsage (apps/server ClaudeAdapter), falling back to the last known usage snapshot on timeout — the existing try/catch only handles rejections, not hangs.
                      • More generally, any await on an SDK control-request Promise inside completeTurn() should be wrapped in a race against a timeout, since a stuck one currently blocks the entire terminal lifecycle event for that turn.

                      Impact

                      Blocks work completely

                      Version or commit

                      T3 Code (Nightly) 0.0.29-nightly.20260724.892

                      Environment

                      No response

                      Logs or stack traces

                      Screenshots, recordings, or supporting files

                      No response

                      Workaround

                      Manually patching the local DB (~/.t3/userdata/state.sqlite, table projection_thread_sessions) — setting status='ready', active_turn_id=NULL for the affected thread — unsticks that specific thread, but the same thing recurs on the very next new session, since the root cause is a per-turn stall, not corrupted state.

                      Metadata

                      Metadata

                      Assignees

                      No one assigned

                        Labels

                        bugSomething is broken or behaving incorrectly.needs-triageIssue needs maintainer review and initial categorization.

                        Type

                        No type

                        Projects

                        No projects

                          Milestone

                          No milestone

                          Relationships

                          None yet

                          Development

                          No branches or pull requests

                          Issue actions

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

                          [Bug]: ⚠️ Claude provider: every turn gets stuck showing "Working..." forever after the CLI already completed — stop button doesn't recover it #4452

                          Description

                          @nikolas-nelson

                          Before submitting

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

                          Area

                          apps/desktop

                          Steps to reproduce

                          Reproduction

                          1. Corporate network with an HTTPS proxy in front of all outbound traffic (relevant — this manifests very consistently in this kind of environment, may not repro on unrestricted networks).
                          2. Open T3 Code, start a brand-new thread, model = Claude (Bedrock), send any message (e.g. "hi").
                          3. The assistant response renders correctly.
                          4. The "Working for Xs" indicator never clears. Clicking Stop repeatedly does nothing.
                          5. Confirmed via local state.sqlite: projection_thread_sessions.status stays running with a stale active_turn_id even though the provider log shows claude/result/success / terminal_reason:completed for that turn.

                          Expected behavior

                          After the Claude provider CLI reports a completed turn (claude/result/success, terminal_reason: completed),
                          the thread session should transition back to ready and accept new messages/turns.

                          Actual behavior:
                          The CLI subprocess completes and streams the correct assistant response, but T3 never emits a turn.completed
                          domain event for it. The thread session stays status: running with a stale active_turn_id forever. The UI
                          shows "Working for Xs" indefinitely, the composer stays locked, and clicking Stop only records an interrupt
                          request — it does not recover the session. This happens on ~100% of turns in my environment (reproduces even on
                          a one-word "hi" with no tools involved). Confirmed via the local state.sqlite (projection_thread_sessions
                          never gets a status:"ready" follow-up event) and per-thread provider logs
                          (~/.t3/userdata/logs/provider/<threadId>.log), which show the CLI reporting success while T3's server-side
                          event log shows nothing after thread.activity-appended.

                          Likely cause (from apps/server/dist/bin.mjs in app.asar): ClaudeAdapter's completeTurn() awaits
                          context.query.getContextUsage() (from @anthropic-ai/claude-agent-sdk) before emitting turn.completed.
                          That SDK call has no timeout on its underlying control-request Promise. In my environment (corporate network,
                          all traffic through an internal HTTPS proxy, Claude via AWS Bedrock) this call appears to hang, which blocks
                          turn.completed from ever being emitted — even though the CLI already finished and streamed its answer.

                          Actual behavior

                          Environment

                          • T3 Code (Nightly) 0.0.29-nightly.20260724.892
                          • macOS (Intel), corporate network with an internal HTTPS proxy in front of all outbound traffic (AWS Bedrock via corporate proxy, not direct Anthropic API)
                          • Provider: Claude Agent (claudeAgent adapter), models eu.anthropic.claude-sonnet-5 / eu.anthropic.claude-opus-4-8 via AWS Bedrock
                          • claude CLI 2.1.218, invoked by T3 with --setting-sources=user,project,local

                          Summary

                          Every single turn, even a trivial one-word prompt like "hi", gets stuck showing "Working for Xm Ys" indefinitely in the UI. Clicking Stop does not recover it — it only records a thread.turn-interrupt-requested domain event but the session never actually returns to ready. This happens on every new thread/session, 100% reproducible, not an occasional race.

                          Root cause (confirmed from logs + DB + app.asar source)

                          1. The claude CLI subprocess spawned by T3 does complete successfully. The per-thread provider log (~/.t3/userdata/logs/provider/<threadId>.log) always shows a clean:
                            claude/result/success ... "terminal_reason":"completed" ... "result":"Ahoj! 👋\n\nJak ti můžu dnes pomoct?"
                            
                          2. T3's server never turns this into a turn.completed domain event. Querying the local event store confirms it:
                            -- ~/.t3/userdata/state.sqliteSELECT event_type FROM orchestration_events WHERE stream_id='<threadId>'ORDER BY sequence;
                            -- thread.created, thread.message-sent, thread.turn-start-requested,-- thread.session-set (status:starting), thread.session-set (status:running),-- thread.message-sent (assistant text), thread.activity-appended-- ... and then NOTHING. No thread.session-set with status:"ready" ever follows.
                          3. projection_thread_sessions for the thread stays status='running' with active_turn_id pointing at the turn that the CLI already finished, forever (until the idle reaper eventually kills it ~30 min later, or the user restarts the app — which does not fix it either, since it's persisted in state.sqlite).
                          4. Clicking Stop repeatedly only emits thread.turn-interrupt-requested (confirmed via orchestration.command.thread.turn.interrupt spans in server.trace.ndjson) — it never flips the session back to ready on a turn the provider has already completed and exited.

                          Where it appears to break (apps/server/dist/bin.mjs, extracted from app.asar)

                          In the Claude adapter's completeTurn() (called from handleResultMessage when a result SDK message with subtype:"success" arrives):

                          constcompleteTurn=Effect.fn("completeTurn")(function*(context,status,errorMessage,result){
                          ...
                          constcontextUsageSnapshot=yield*queryCurrentContextUsage(context, ...);
                          ...
                          yield*offerRuntimeEvent({type: "turn.completed", ... });// <-- never reached
                          ...
                          });constqueryCurrentContextUsage=Effect.fn("queryCurrentContextUsage")(function*(context,totalProcessedTokens){if(!context.query.getContextUsage)return;constusage=yield*Effect.promise(async()=>{try{returnawaitcontext.query.getContextUsage?.();}catch{return;}});
                          ...
                          });

                          context.query.getContextUsage() comes from @anthropic-ai/claude-agent-sdk's Query.getContextUsage():

                          asyncgetContextUsage(){return(awaitthis.request({subtype:"get_context_usage"})).response}

                          which goes through the SDK's internal request() control-protocol call:

                          request(e){lett=Math.random().toString(36).substring(2,15);
                          ...
                          returnnewPromise((o,i)=>{this.pendingControlResponses.set(t,{handler: ...,reject: i});Promise.resolve(this.transport.write(...)).catch((s)=>{this.pendingControlResponses.delete(t);i(s)});});}

                          This Promise has no timeout. If the CLI subprocess never sends back a control_response for the get_context_usage control request (e.g. because it's blocked on an internal network call under a corporate proxy, or any other stall), the await in queryCurrentContextUsage hangs forever. Since completeTurn() awaits this before emitting turn.completed, the whole turn-completion pipeline silently deadlocks even though the CLI already streamed the final result message and the user-visible answer.

                          This would explain why:

                          • The visible chat response ("Hi! 👋 ....") does show up (it comes from the earlier assistant message handling, before completeTurn runs).
                          • The turn never settles to ready, so the UI shows "Working..." forever.
                          • Stop does nothing useful — interrupting a turn whose SDK query object is itself hung on an unrelated internal request doesn't unstick that hung await.
                          • This reproduces on literally every message ("hi" included), not just tool-heavy turns — consistent with a stall in a fixed post-turn step (getContextUsage) that runs on every single turn completion, not with something related to turn content.

                          I don't have 100% certainty this exact control request is what's hanging (I was not able to attach a debugger to the running claude CLI process to confirm), but the DB/log evidence rules out everything before completeTurn(), and the code path is the only awaited, unbounded, non-timed-out promise between "CLI reports success" and "turn.completed is emitted."

                          Suggested fix

                          • Add a timeout (e.g. 5–10s) around context.query.getContextUsage() in queryCurrentContextUsage (apps/server ClaudeAdapter), falling back to the last known usage snapshot on timeout — the existing try/catch only handles rejections, not hangs.
                          • More generally, any await on an SDK control-request Promise inside completeTurn() should be wrapped in a race against a timeout, since a stuck one currently blocks the entire terminal lifecycle event for that turn.

                          Impact

                          Blocks work completely

                          Version or commit

                          T3 Code (Nightly) 0.0.29-nightly.20260724.892

                          Environment

                          No response

                          Logs or stack traces

                          Screenshots, recordings, or supporting files

                          No response

                          Workaround

                          Manually patching the local DB (~/.t3/userdata/state.sqlite, table projection_thread_sessions) — setting status='ready', active_turn_id=NULL for the affected thread — unsticks that specific thread, but the same thing recurs on the very next new session, since the root cause is a per-turn stall, not corrupted state.

                          Metadata

                          Metadata

                          Assignees

                          No one assigned

                            Labels

                            bugSomething is broken or behaving incorrectly.needs-triageIssue needs maintainer review and initial categorization.

                            Type

                            No type

                            Projects

                            No projects

                              Milestone

                              No milestone

                              Relationships

                              None yet

                              Development

                              No branches or pull requests

                              Issue actions

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

                              [Bug]: ⚠️ Claude provider: every turn gets stuck showing "Working..." forever after the CLI already completed — stop button doesn't recover it #4452

                              Description

                              @nikolas-nelson

                              Before submitting

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

                              Area

                              apps/desktop

                              Steps to reproduce

                              Reproduction

                              1. Corporate network with an HTTPS proxy in front of all outbound traffic (relevant — this manifests very consistently in this kind of environment, may not repro on unrestricted networks).
                              2. Open T3 Code, start a brand-new thread, model = Claude (Bedrock), send any message (e.g. "hi").
                              3. The assistant response renders correctly.
                              4. The "Working for Xs" indicator never clears. Clicking Stop repeatedly does nothing.
                              5. Confirmed via local state.sqlite: projection_thread_sessions.status stays running with a stale active_turn_id even though the provider log shows claude/result/success / terminal_reason:completed for that turn.

                              Expected behavior

                              After the Claude provider CLI reports a completed turn (claude/result/success, terminal_reason: completed),
                              the thread session should transition back to ready and accept new messages/turns.

                              Actual behavior:
                              The CLI subprocess completes and streams the correct assistant response, but T3 never emits a turn.completed
                              domain event for it. The thread session stays status: running with a stale active_turn_id forever. The UI
                              shows "Working for Xs" indefinitely, the composer stays locked, and clicking Stop only records an interrupt
                              request — it does not recover the session. This happens on ~100% of turns in my environment (reproduces even on
                              a one-word "hi" with no tools involved). Confirmed via the local state.sqlite (projection_thread_sessions
                              never gets a status:"ready" follow-up event) and per-thread provider logs
                              (~/.t3/userdata/logs/provider/<threadId>.log), which show the CLI reporting success while T3's server-side
                              event log shows nothing after thread.activity-appended.

                              Likely cause (from apps/server/dist/bin.mjs in app.asar): ClaudeAdapter's completeTurn() awaits
                              context.query.getContextUsage() (from @anthropic-ai/claude-agent-sdk) before emitting turn.completed.
                              That SDK call has no timeout on its underlying control-request Promise. In my environment (corporate network,
                              all traffic through an internal HTTPS proxy, Claude via AWS Bedrock) this call appears to hang, which blocks
                              turn.completed from ever being emitted — even though the CLI already finished and streamed its answer.

                              Actual behavior

                              Environment

                              • T3 Code (Nightly) 0.0.29-nightly.20260724.892
                              • macOS (Intel), corporate network with an internal HTTPS proxy in front of all outbound traffic (AWS Bedrock via corporate proxy, not direct Anthropic API)
                              • Provider: Claude Agent (claudeAgent adapter), models eu.anthropic.claude-sonnet-5 / eu.anthropic.claude-opus-4-8 via AWS Bedrock
                              • claude CLI 2.1.218, invoked by T3 with --setting-sources=user,project,local

                              Summary

                              Every single turn, even a trivial one-word prompt like "hi", gets stuck showing "Working for Xm Ys" indefinitely in the UI. Clicking Stop does not recover it — it only records a thread.turn-interrupt-requested domain event but the session never actually returns to ready. This happens on every new thread/session, 100% reproducible, not an occasional race.

                              Root cause (confirmed from logs + DB + app.asar source)

                              1. The claude CLI subprocess spawned by T3 does complete successfully. The per-thread provider log (~/.t3/userdata/logs/provider/<threadId>.log) always shows a clean:
                                claude/result/success ... "terminal_reason":"completed" ... "result":"Ahoj! 👋\n\nJak ti můžu dnes pomoct?"
                                
                              2. T3's server never turns this into a turn.completed domain event. Querying the local event store confirms it:
                                -- ~/.t3/userdata/state.sqliteSELECT event_type FROM orchestration_events WHERE stream_id='<threadId>'ORDER BY sequence;
                                -- thread.created, thread.message-sent, thread.turn-start-requested,-- thread.session-set (status:starting), thread.session-set (status:running),-- thread.message-sent (assistant text), thread.activity-appended-- ... and then NOTHING. No thread.session-set with status:"ready" ever follows.
                              3. projection_thread_sessions for the thread stays status='running' with active_turn_id pointing at the turn that the CLI already finished, forever (until the idle reaper eventually kills it ~30 min later, or the user restarts the app — which does not fix it either, since it's persisted in state.sqlite).
                              4. Clicking Stop repeatedly only emits thread.turn-interrupt-requested (confirmed via orchestration.command.thread.turn.interrupt spans in server.trace.ndjson) — it never flips the session back to ready on a turn the provider has already completed and exited.

                              Where it appears to break (apps/server/dist/bin.mjs, extracted from app.asar)

                              In the Claude adapter's completeTurn() (called from handleResultMessage when a result SDK message with subtype:"success" arrives):

                              constcompleteTurn=Effect.fn("completeTurn")(function*(context,status,errorMessage,result){
                              ...
                              constcontextUsageSnapshot=yield*queryCurrentContextUsage(context, ...);
                              ...
                              yield*offerRuntimeEvent({type: "turn.completed", ... });// <-- never reached
                              ...
                              });constqueryCurrentContextUsage=Effect.fn("queryCurrentContextUsage")(function*(context,totalProcessedTokens){if(!context.query.getContextUsage)return;constusage=yield*Effect.promise(async()=>{try{returnawaitcontext.query.getContextUsage?.();}catch{return;}});
                              ...
                              });

                              context.query.getContextUsage() comes from @anthropic-ai/claude-agent-sdk's Query.getContextUsage():

                              asyncgetContextUsage(){return(awaitthis.request({subtype:"get_context_usage"})).response}

                              which goes through the SDK's internal request() control-protocol call:

                              request(e){lett=Math.random().toString(36).substring(2,15);
                              ...
                              returnnewPromise((o,i)=>{this.pendingControlResponses.set(t,{handler: ...,reject: i});Promise.resolve(this.transport.write(...)).catch((s)=>{this.pendingControlResponses.delete(t);i(s)});});}

                              This Promise has no timeout. If the CLI subprocess never sends back a control_response for the get_context_usage control request (e.g. because it's blocked on an internal network call under a corporate proxy, or any other stall), the await in queryCurrentContextUsage hangs forever. Since completeTurn() awaits this before emitting turn.completed, the whole turn-completion pipeline silently deadlocks even though the CLI already streamed the final result message and the user-visible answer.

                              This would explain why:

                              • The visible chat response ("Hi! 👋 ....") does show up (it comes from the earlier assistant message handling, before completeTurn runs).
                              • The turn never settles to ready, so the UI shows "Working..." forever.
                              • Stop does nothing useful — interrupting a turn whose SDK query object is itself hung on an unrelated internal request doesn't unstick that hung await.
                              • This reproduces on literally every message ("hi" included), not just tool-heavy turns — consistent with a stall in a fixed post-turn step (getContextUsage) that runs on every single turn completion, not with something related to turn content.

                              I don't have 100% certainty this exact control request is what's hanging (I was not able to attach a debugger to the running claude CLI process to confirm), but the DB/log evidence rules out everything before completeTurn(), and the code path is the only awaited, unbounded, non-timed-out promise between "CLI reports success" and "turn.completed is emitted."

                              Suggested fix

                              • Add a timeout (e.g. 5–10s) around context.query.getContextUsage() in queryCurrentContextUsage (apps/server ClaudeAdapter), falling back to the last known usage snapshot on timeout — the existing try/catch only handles rejections, not hangs.
                              • More generally, any await on an SDK control-request Promise inside completeTurn() should be wrapped in a race against a timeout, since a stuck one currently blocks the entire terminal lifecycle event for that turn.

                              Impact

                              Blocks work completely

                              Version or commit

                              T3 Code (Nightly) 0.0.29-nightly.20260724.892

                              Environment

                              No response

                              Logs or stack traces

                              Screenshots, recordings, or supporting files

                              No response

                              Workaround

                              Manually patching the local DB (~/.t3/userdata/state.sqlite, table projection_thread_sessions) — setting status='ready', active_turn_id=NULL for the affected thread — unsticks that specific thread, but the same thing recurs on the very next new session, since the root cause is a per-turn stall, not corrupted state.

                              Metadata

                              Metadata

                              Assignees

                              No one assigned

                                Labels

                                bugSomething is broken or behaving incorrectly.needs-triageIssue needs maintainer review and initial categorization.

                                Type

                                No type

                                Projects

                                No projects

                                  Milestone

                                  No milestone

                                  Relationships

                                  None yet

                                  Development

                                  No branches or pull requests

                                  Issue actions