Skip to content

Copilot thread cannot continue after handoff_budget_exhausted triggers unsupported fresh-session fallback #39

Open

Description

@jdbencardinop

Report

An existing Copilot-backed T3 thread reportedly becomes unable to continue even though its saved native session remains intact. A separate investigating agent reports that native session reopening succeeded, after which T3 selected handoff_budget_exhausted and attempted a fresh-session fallback that the Copilot integration could not perform.

This issue records that agent's findings; the exact trace and independent reproduction are still pending. Do not yet assume which adapter capability or orchestration decision is responsible, or that every resume is affected. Native CLI resume was offered as a workaround; its success has not been independently confirmed here.

Reported sequence

  1. Create a Copilot conversation in T3 and accumulate conversation history.
  2. Later continue/reopen the same thread.
  3. Native reopening reportedly succeeds.
  4. T3 reportedly determines that the earlier-conversation handoff exceeds its budget (handoff_budget_exhausted).
  5. A fresh-session fallback fails, leaving the existing T3 thread unable to continue.

The precise history threshold and triggering conditions need evidence; these are not yet deterministic reproduction steps.

Scope

Investigate existing-thread continuation/resume, history-handoff admission and the Copilot fresh-session fallback. Keep this separate from #38 (standalone extension initialization at session creation) and #33 (importing external sessions).

Copilot CLI experimental flags being persisted to its own profile are outside this issue. The reporting agent states experimental mode was already disabled during this later failure. No change to native CLI settings or profile isolation is requested here.

Evidence requested from the investigating agent

Please comment with:

  • Installed T3 source/version, Copilot SDK/runtime versions and whether this is a packaged build.
  • Sanitized chronological trace showing successful native reopen, the budget decision, attempted fallback and exact terminal error.
  • Why the handoff was required after native reopening: whether native resume was actually reusable, or a context transfer/new session was intentionally necessary.
  • The relevant fallback capability/error and smallest harmless reproduction, including approximate history size and configured budget.
  • Whether repeated continuation fails, whether direct native CLI resume actually works, and whether the same behavior occurs with experimental mode disabled.

Do not post raw conversations, screenshots, credentials, machine names/addresses, personal filesystem paths, or full native session identifiers. Short consistent aliases are sufficient for correlation.

Expected behavior

A supported existing thread should remain continuable when its native session can be safely resumed. If history must be transferred and exceeds the budget, provide an explicit supported recovery path rather than repeatedly selecting an unsupported fallback. Preserve original history/session identity and expose context-loss/ownership boundaries; do not silently truncate history, reset credentials, or replace the conversation.

No source fix or live-state modification is included in this report.

Activity

  1. jdbencardinop commented on Nov 5, 2026

    @jdbencardinop
    Author

    Case evidence from the affected installation

    The thread was created inside T3, completed six runs, then failed when the user sent another message. It was not an imported CLI conversation. A later continuation successfully reopened the native session but still failed before starting the provider turn. The user intentionally settled the thread after these failures; settling did not trigger them.

    I rechecked retained provider logs, T3 trace spans, read-only persisted projections, and the installed runtime's embedded source. The terminal failure and fallback sequence are confirmed locally. A clean-room reproduction and a source fix are not included.

    Environment

    • Packaged Linux/WSL runtime: t3 v0.1.4-preview.20261103.1.
    • Installed runtime artifact: sha256-6ddb2660d5339b0cc6f17c53d92774f1496e0d68df676d3ccbdee495a63707f4.
    • Bundled @github/copilot-sdk and @github/copilot-sdk-linux-x64: 1.0.28.
    • Native session creation metadata: Copilot 1.0.105; the currently installed CLI also reports 1.0.105.
    • All eight runs selected model: auto, autoTier: hydrafusion-max (UI: Fusion / Max). There was no recorded selection change between the successful and failed runs.

    Use aliases thread-A / native-A for this case. No raw conversations, screenshots, filesystem paths, or native identifiers are attached.

    What happened

    All timestamps are UTC.

    1. 2026-11-04 19:52:54–22:20:22: runs 1–6 completed in the same T3-created conversation. Work included developing/testing an OMC plugin. This establishes chronology, not causation by OMC.

    2. 22:58:52–22:58:59: run 7 failed before provider-turn start. Its recovery reason was resume_failed. This is the earlier failure; it is not evidence that the later attempt failed to reopen.

    3. 2026-11-05 01:13:16.154: user submitted run 8 to the existing thread.

    4. 01:13:17.116–17.341: provider log recorded session.resume, followed by initialization events including tools/settings/skills updates. The corresponding initial CopilotAdapter.ensureThread span completed successfully.

    5. 01:13:17.357: orchestrationV2.deliverContextHandoffs failed with:

      ContextHandoffBudgetError:
      The request leaves too little context for a provider handoff.
      Shorten the request or use a larger-context model;
      the current request has not been truncated.
      
    6. 01:13:17.406: the subsequent handoff check using fresh-session context succeeded.

    7. 01:13:17.417: T3 logged:

      Recovering provider context in a fresh native session
      driver: githubCopilot
      reason: handoff_budget_exhausted
      errorTag: ContextHandoffBudgetError
      

      It called ensureThread with forceNewNativeThread: true. The Copilot adapter explicitly rejects that request:

      Native Copilot session replacement is unsupported.
      
    8. The same budget/fallback failure occurred across five internal attempts for run 8, ending at 01:13:19.213. The UI showed “Provider turn failed to start” and the replacement error. These were retries of one user submission, not five independently reproduced continuations.

    Why a handoff after successful reopen?

    The installed orchestration code prepares a delta_since_target_last_seen retry handoff for prior failed/interrupted inputs that were never accepted natively. Run 7 fits that condition: its attempt has no provider turn or native-thread assignment. Its user message was 345 UTF-8 bytes.

    This makes recovery of the previous undelivered input the likely reason for the handoff, rather than a need to replay the entire successful conversation. The generated handoff was not persisted, so its exact contents are not independently captured. Run 8's current user message was 2,815 UTF-8 bytes with no attachments.

    Strong candidate: cumulative usage treated as native occupancy

    The persisted values disagree in a way that explains the budget failure:

    providerThread.contextUsage:
      usedTokens = 120615
      maxTokens = 200000
      compactsAutomatically = true
    
    latest completed root providerTurn.tokenUsage (run 6):
      inputTokens = 1162188
      outputTokens = 7222
      usedTokens = 1169410
    

    In the installed Copilot adapter, session.usage_info updates session occupancy, while assistant.usage accumulates input/output usage across requests within a turn.

    However, latestNativeContextUsage reads the latter providerTurn.tokenUsage.usedTokens. Run 6 satisfies its native-session/root-node matching conditions. handoffContextForThread merges that value over the thread's occupancy telemetry. handoffBudget then subtracts it from the available context window, clamping the result to zero. This uses cumulative request accounting as though it were current context occupancy.

    The packaged default handoff cap is 16,000 (T3CODE_CONTEXT_HANDOFF_TOKEN_CAP); I have not independently established a process-level override. The much larger cumulative usage would exhaust the budget regardless of that cap.

    This is a source-and-state explanation consistent with the observed trace, not an instrumented capture of the budget arguments at failure time.

    Remaining boundaries

    • Fusion was enabled, but there is no non-Fusion comparison. Investigate usage semantics and handoff admission at the Copilot/orchestration boundary before calling this Fusion-specific.
    • OMC development preceded the failure; there is no evidence here that OMC corrupted the saved session.
    • The earlier resume_failed path and this later handoff_budget_exhausted path converge on the same unsupported fallback. Identical UI text does not establish an identical initial cause.
    • Direct native CLI resume was suggested previously but has not been executed/verified as a workaround for this case.
    • Native reopening succeeded; continuation after bypassing the budget check has not been tested.

    Suggested narrow regression: resume an unchanged native session with valid occupancy below its context limit, cumulative per-turn request usage above that limit, and one prior failed input requiring retry handoff. Verify that admission uses occupancy rather than cumulative usage, and that recovery never selects an unsupported replacement operation.

  2. jdbencardinop commented on Nov 5, 2026

    @jdbencardinop
    Author

    Independent verification and isolated continuation reproduction

    Investigation only; no source fix, integration, or deployment. The preceding report remains credible, with two independently checked but distinct paths.

    1. Accounting/budget explanation verified at helper level

    On candidate df3383e475c3d052bcac52259384cec319dc6901, the source confirms:

    • Copilot session occupancy comes from session.usage_info.
    • assistant.usage accumulates request accounting within a turn.
    • latestNativeContextUsage reads that cumulative providerTurn.tokenUsage.usedTokens.
    • handoffContextForThread merges it over session occupancy before budgeting.
    • The fresh-session recovery path calls forceNewNativeThread: true, which the Copilot adapter explicitly rejects.

    A private harness imported the actual exported budget helpers, using synthetic metadata containing the preceding report's sanitized numbers and a 2,815-byte placeholder request, with no attachments:

    Input to budgeting Available handoff budget
    Native occupancy: 120,615 / 200,000 16,000
    After the current merge overwrites usage with 1,169,410 0
    Fresh context 16,000

    This independently reproduces the numeric accounting error. It is not a new long-conversation reproduction or an instrumented capture of the original production call.

    2. Requested experimental-on/off application sequence

    Used a newly created Copilot profile, separate disposable T3 state, an empty fixture Git project, SDK 1.0.28 / native runtime 1.0.105, and the same T3-created thread throughout. Selection remained Auto + hydrafusion-max. Only harmless one-line messages were sent; no real user profile, thread, or installation was modified. Commands followed the application's typed WebSocket orchestration path, and completion was observed through terminal run events.

    Step Result
    experimental: false; create thread and send Completed
    Enable experimental in isolated config; send using the existing warm runtime Completed
    Restart only the disposable backend; continue with experimental enabled Failed
    Restore the isolated config byte-for-byte, including experimental: false; restart and continue Failed

    Important distinction: both failed continuations initially failed native session.resume with:

    Failed to initialize standalone session extensions:
    extensionSdkPath is required when standalone extensions are requested
    

    Both then reached T3's resume_failed recovery path and the same terminal error:

    Native Copilot session replacement is unsupported.
    

    Thus the proposed sequence reproduces two failed application continuations, including one after config restoration, but does not reproduce the successful-native-resume → handoff_budget_exhausted path described in the preceding comment. The latest completed turn's usage was only 24,037 tokens, with native occupancy 28,968 / 272,000; this was not the reported >1M cumulative-usage case.

    A separate headless CLI session with --experimental did not persist the flag in this runtime. The application experiment therefore deliberately toggled the fresh test profile's configuration and restored it exactly. This is a configuration-state reproduction, not evidence about the CLI's flag-persistence semantics, which remain outside this issue's scope.

    3. Native controls and scope

    • With the affected test profile restored to experimental: false, native resume with requestExtensions omitted still failed on extension initialization.
    • Explicit requestExtensions: false reopened that same saved native session successfully, without sending a prompt. This is a diagnostic control, not a shipped application fix or proof that application continuation works.
    • In a second fresh profile that was never experimental, one harmless concrete-model message was persisted. Cold resume succeeded both with requestExtensions omitted and with explicit false.
    • An initial no-message control was unusable because its empty session was not retained; the persisted-message control above replaces it.

    The persistence mechanism behind the affected session/profile's extension requirement has not been localized. Do not infer that every cold resume fails, that the saved conversation was lost, or that the budget bug is Fusion-specific.

    The extension-startup evidence also belongs to #38. Keep #39's accounting/admission and unsupported-recovery findings in the backlog; a future regression should exercise the original budget path separately from this extension-triggered path.

    All evidence here is sanitized text. No private transcript, screenshot, token, native identifier, machine name, or local filesystem path is attached.

  3. jdbencardinop commented on Nov 5, 2026

    @jdbencardinop
    Author

    Implemented on fix/copilot-context-occupancy (branch head). Copilot declares that per-turn usage is cumulative billing, so handoff admission retains native occupancy rather than replacing it with billed totals, including saved historical turns. It also explicitly declines native replacement: resume/budget failures retain their original error instead of invoking the known-unsupported operation. The original conversation is not truncated/replaced. Persisted orchestration fixtures verify the reported 120615/200000 occupancy with 1169410 cumulative usage, unchanged native identity and billing counters; genuine budget failure and failed resume retain identity without a replacement prompt. 96 focused tests and all 113 adjacent changed-file tests pass; affected typechecks/format/lint pass. Known baseline knip and historical tpatch parent-replay blockers remain. This is validated source plus an isolated orchestration reproduction, not a replay of the private long conversation or a deployment.

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions