Skip to content

[Bug]: Session remains permanently stuck after temporary provider failure during /goal #425

Closed

Description

@TheBaby5

Product or interface

CLI - interactive TUI

Version

0.6.2

Platform

Linux

OS version and architecture

Ubuntu 24.04.5 LTS x64

Issue area

Agent stuck / slow / repeated loop

Desktop log upload ID (optional)

N/A - CLI

Steps to reproduce

Observed sequence (not yet confirmed as deterministic):

  1. Start a session with /goal active and running autonomous turns.
  2. While /goal is running, the BYOK provider temporarily becomes unavailable.
  3. MCode reports provider error 50113.
  4. The provider later recovers; other/new MCode sessions work normally.
  5. The affected session does not recover:
    • /goal remains stuck/Loading
    • normal messages such as "hi" or "continue" do not execute
    • /retry does not recover it
    • /goal clear does not recover ordinary chat
  6. The only workaround is abandoning the affected session and opening a new one.

Expected and actual behavior

EXPECTED

provider temporarily fails during /goal
↓
current turn fails/retries/pauses safely
↓
provider comes back
↓
session accepts turns normally again

ACTUAL

provider temporarily fails during /goal
↓
error 50113
↓
provider comes back
↓
affected session remains stuck
↓
/goal cannot continue
↓
ordinary chat also cannot execute
↓
/goal clear does not recover session
↓
must create a new session

===

Technical note for maintainers:

Inspection suggests a possible ACP session recovery/cleanup issue.

  • Failed/aborted ACP interactions can remain detached/pending.
  • The runtime has a 32-interaction limit; exhaustion can close the ACP connection.
  • This would explain why ordinary turns also stop working after the provider recovers.
  • /retry uses the same affected session/connection.
  • /goal clear clears Goal state but does not appear to reset the underlying ACP connection.
  • New sessions work normally.

Suspected area: ACP interaction cleanup / session connection recovery after provider failure.

This is a suspected root cause from runtime inspection, not a confirmed diagnosis.

Redacted error summary

Error: The model provider is temporarily unavailable.
Code: 50113
Run /retry to resend your last message. Your prompt is preserved.

After the provider recovered, other/new MCode sessions worked normally,
but this specific session remained unusable.

Normal turns did not execute, /retry did not recover it, and /goal clear
did not restore the session.

Screenshots

  1. 50113 error after temporary provider outage during /goal.
  2. Affected session stuck after provider recovery.
  3. Repeated normal messages ("continue", "hi") not executing.

Sensitive paths/project information redacted where applicable.

Image

Before submitting

  • I have searched existing issues.
  • I have included my version and removed sensitive information.

Activity

  1. added
    bugSomething isn't working
    needs-triageAwaiting maintainer assessment
    on Oct 3, 2026
  2. hetaoBackend commented on Oct 4, 2026

    @hetaoBackend
    Collaborator

    This was generated by AI during triage.

    @TheBaby5 — thanks for the structured repro and, importantly, for being explicit that the ACP root cause is a suspected diagnosis from inspection rather than a confirmed one. That framing makes this triageable. I verified the concrete mechanism you suspected, and it checks out.

    Verification (against main)

    The limits you inferred are real and literally named in the ACP agent:

    • packages/tui/src/acp/agent.ts:48-49 — MAX_DETACHED_LIFECYCLES_PER_SESSION = 2 and MAX_DETACHED_LIFECYCLES_TOTAL = 32.
    • :50-51 — MAX_ADMITTED_LIFECYCLES_PER_SESSION = 64 / MAX_ADMITTED_LIFECYCLES_TOTAL = 256.

    So the "32-interaction limit" you suspected does exist, and there is a real detached-lifecycle accounting path (detachedGenerations, detachedGenerationCount, gated at :1868-1869 and :1891-1892). Your hypothesis — that failed/aborted ACP interactions can stay detached/pending and eventually starve the session — is grounded in the actual control flow, not invented. Also consistent with your observation that new sessions work fine while the affected one doesn't: the exhaustion is per-connection/per-session state, not global.

    Assessment

    • Category: bug (as filed) — a session that can never recover after a transient provider failure is a serious usability failure: the user is forced to abandon the session entirely, and (worse) any in-flight work state tied to it.
    • Your step-by-step "ACTUAL" flow is the right shape for the bug: the provider recovers, but the session's ACP connection never recovers, so even ordinary turns and /goal clear can't run. That "provider is fine, but this session is bricked" split is the key symptom to fix.
    • Whether the detached-lifecycle exhaustion is the exact cause still needs a live repro — you were appropriately honest that it's unconfirmed, and I'd keep it as the leading hypothesis rather than a conclusion. A maintainer reproducing it (or a run that logs detached-count on failure) would confirm or refute it quickly.

    Classification

    • State: needs-triage — plausible, partially-verified root cause; needs a maintainer with runtime access to reproduce and confirm (or refute) the detached-exhaustion theory. (This tracker has no ready-for-human label, so needs-triage + this note is the routing.)

    What would help most

    • Whether the failure correlates with hitting MAX_DETACHED_LIFECYCLES_TOTAL (32) or the per-session cap (2) — if you can tell which, that pins the mechanism.
    • Whether /goal is necessary to trigger it, or whether a plain turn that fails mid-stream during a provider outage can brick a session the same way. That determines whether this is Goal-specific or a general session-recovery bug.

    Your instinct to point at ACP interaction cleanup — and to explicitly note it's suspected, not confirmed — is exactly the right level of rigor. Happy to re-triage with a confirmed repro if you can capture one.

    No credentials or sensitive data echoed; the ACP session-cap reasoning is from source, which is public here.

  3. TheBaby5 commented on Oct 4, 2026

    @TheBaby5
    Author

    Thanks — I still had the affected old session, so I inspected its preserved runtime/SQLite state read-only to answer the two questions you asked.

    1. Was it the per-session cap (2) or total cap (32)?

    I cannot prove which cap was hit.

    The detached-lifecycle counters appear to be in-memory only; I could not find historical values persisted in SQLite, the session ledger, or durable logs. So after the fact there is no reliable way to reconstruct whether this hit:

    • MAX_DETACHED_LIFECYCLES_PER_SESSION = 2
    • MAX_DETACHED_LIFECYCLES_TOTAL = 32

    I do not want to claim either one without evidence.

    What is preserved is the failure boundary: after the bad turn, subsequent ordinary turns were accepted into turn_ingress but ended aborted / failed without reaching an LLM or tool call.

    "continue" → status=aborted
    "hi"       → status=aborted
    "hi"       → status=failed
    

    So the session remained stuck below the Goal layer even though other/new sessions worked normally.

    2. Is /goal required?

    The evidence suggests this may be a general session-recovery bug rather than Goal-specific, but I cannot claim a live non-Goal reproduction yet.

    Source-level:

    • Goal and ordinary turns share the same ACP interaction/admission path.
    • The admission rejection mechanism is session-level, not Goal-specific.

    Preserved-session evidence:

    • the later failed messages are ordinary turns;
    • /goal clear did not restore them;
    • local_runtime_thread_goals was empty when inspected;
    • no explicit mavis-internal:goal:* turns remained in turn_ingress.

    One important correction from my original report: the durable session record shows the triggering provider-side failure as:

    400 Upstream
    [invalid_request_error] Too many images in request: 31 > 30
    

    MCode surfaced the session-level error as 50113.

    So I can no longer prove from the preserved state that the original trigger was specifically a provider outage during /goal.

    What I can prove is:

    provider-side turn failure
    → session enters error/stuck state
    → subsequent ordinary turns are aborted/failed before execution
    → clearing Goal state does not recover the session
    → new sessions continue working normally
    

    That makes the more useful bug boundary look like ACP/session admission recovery after a failed provider turn, with /goal potentially only being how I originally encountered it.

    I still have the preserved affected session and can inspect further if you want a specific state/log/source path checked.

  4. hetaoBackend commented on Oct 4, 2026

    @hetaoBackend
    Collaborator

    Thanks for the report. We tried to reproduce this with the interactive TUI against a provider failing with 5xx, connection resets and mid-stream cuts, and in each case the session recovered once the provider came back (hi, /retry and /goal resume all worked). The only stuck state we could produce was a request that never got a response, which #428 now bounds.

    To narrow down your case, could you share:

    1. Are you running mcode through mcode acp (e.g. an editor/IDE integration), or the interactive terminal UI?
    2. Roughly how long did you wait after the provider recovered, and did pressing Esc help?
    3. The runtime log from the stuck session, if you still have it.
  5. TheBaby5 commented on Oct 4, 2026

    @TheBaby5
    Author

    For clarity: the old affected session remains stuck/retrying with 50113...

    Image Image

    while a new session using the exact same API key + provider works normally at the same time — so the provider/key is healthy and the failure is isolated to that session.

    Image
  6. TheBaby5 commented on Oct 4, 2026

    @TheBaby5
    Author

    One extra concrete data point from the exported affected session:

    • 263 turns / 3,429 messages
    • 44 image attachments exist across the session history
    • near the end, the exact same screenshot was attached again in Turns 259, 260, 261 and 262

    So the 31 > 30 images rejection is consistent with cumulative image history being resent with later turns, not with the provider/key being unhealthy. :chatgpt-content-reference{index="0"} :chatgpt-content-reference{index="1"}

    A regression test that would directly cover my case:

    session accumulates >30 historical images
    → send a normal text-only message
    → MCode builds the next provider request
    
  7. added a commit that references this issue on Oct 4, 2026
  8. hetaoBackend commented on Oct 5, 2026

    @hetaoBackend
    Collaborator

    Hi @TheBaby5, thanks a lot for the detailed report and the regression-test idea. It made this easy to pin down.

    We reproduced it locally: pasted images stay in the session history, and every request re-sends the whole history, so once a session holds more than 30 images every later request (even plain text, /retry, /compact, or a restarted session) gets Too many images in request: 31 > 30. Each failure was also retried several times behind a generic "temporarily unavailable" message.

    Draft PR #431 addresses this: requests now carry only the most recent images (20 by default, configurable per model with max_images_per_request), with older ones replaced by a short note naming the file; this rejection now fails once with the provider's reason and a /compact or /new hint; and /compact falls back to an image-free summary so a stuck session can recover.

    Two quick questions that would help us confirm it covers your setup: which provider/gateway (and model) were you using, and were the images mostly pasted screenshots or produced by tools (e.g. browser screenshots)? If you're able to try the branch, feedback is very welcome.

  9. TheBaby5 commented on Oct 5, 2026

    @TheBaby5
    Author

    Thanks — I tested #431 against copies of my actual preserved affected sessions, using the same real provider/gateway/model. The fix works.

    Provider / gateway / model

    • Provider: custom_provider:opencode-go
    • Gateway: OpenCode Zen (Go)
    • Base URL: https://opencode.ai/zen/go/v1
    • Model: deepseek-v4.1-flash
    • API: openai-completions
    • BYOK with my own API key

    This is recorded in the affected sessions' llm-call.json and also appears in the original BYOK error itself.

    Image source

    The images were mostly tool-generated, not pasted by me.

    Session | Total images | Tool-generated (read) | User-pasted -- | -- | -- | -- old post drill | 31 | 24 | 7 pc old | 34 | 27 | 7

    So roughly 77–79% were tool-generated. They came from the read tool reading image files, not browser-screenshot tool output directly.

    One correction to my earlier comment: the authoritative provider-visible image counts in the preserved sessions are 31 and 34, not 44. The provider's own errors (31 > 30, 34 > 30) match those stored inline-image counts exactly.

    Real #431 validation

    I built merge commit 1852771 in an isolated temporary checkout and tested it against copies of both broken sessions, with the same real OpenCode Zen gateway + DeepSeek V4.1 Flash.

    old post drill
    31 stored images
    → 20 sent
    → 11 replaced with placeholders
    → plain "hi" succeeded
    → second turn succeeded
    → session recovered ✅
    

    pc old
    34 stored images
    → 20 sent
    → 14 replaced with placeholders
    → turn succeeded
    → session recovered ✅

    The stored histories remained unchanged; only the outbound request copy was capped. I also verified the original session files were byte-identical by SHA-256 before and after testing.

    The effective max_images_per_request on my setup is the default 20, which is safely below this gateway's observed limit of 30.

    I also verified the error-classification change against the real preserved error string: it is now recognized as a deterministic image_limit rejection, so the pointless retry loop is suppressed, while normal transient errors such as 503, 429, and ECONNRESET remain retryable.

    I couldn't drive a live TUI /compact headlessly, but the merged compactContext provider image-limit recovery (#425) tests all pass, including the image-free fallback path.

    One practical note: my installed MCode was still 0.6.1, which does not contain this fix. The isolated 0.6.2 build does.

    So from my side: #431 fixes the real issue on both affected sessions.

  10. hetaoBackend commented on Oct 6, 2026

    @hetaoBackend
    Collaborator

    Hi @TheBaby5, thank you for testing #431 against copies of both of your real stuck sessions, on the same gateway and model, and for checking that the original session files were left untouched. That was exactly the confirmation we needed.

    To sum up for anyone who finds this later: the stuck session came from image history being re-sent on every request. Once a session held more than 30 images, each request got Too many images in request: 31 > 30, retried several times behind a generic error. #431 (merged as 1852771) now sends only the most recent images (20 by default, configurable per model with max_images_per_request) and replaces older ones with a short placeholder. It fails this rejection once with the provider's reason and a /compact or /new hint, and /compact falls back to an image-free summary. Separately, #428 bounds requests that never get a response.

    The fix will ship in the next release; the 0.6.1 you have installed doesn't include it. I'm closing this as fixed. If a session gets stuck again after upgrading, please reopen or file a new issue with the error text and we'll take another look.

  11. added a commit that references this issue on Oct 8, 2026
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

    bugSomething isn't workingtuiInteractive terminal UI (TUI)

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions