Skip to content

ACP v2 idle errors are discarded and failed turns are marked completed #21219

Closed

Description

@anesthetised

Description

When an ACP v2 agent accepts session/prompt and later reports an error through an idle state_update, T3 marks the turn as completed without showing the error.

This happened with acplause when OpenRouter rejected a request with HTTP 401. Fixing the API key restored normal responses, but T3 had hidden the authentication failure.

Environment

  • T3 Code Beta 0.1.0, macOS
  • ACP registry provider: acplause
  • Negotiated protocol version: 2
  • T3 source inspected: 9c16146

The relevant behavior was also confirmed in the installed app bundle.

Draft-version compatibility clarification

The two implementations use different ACP v2 draft revisions:

  • acplause pins 2.0.0-alpha.9 at upstream commit 3be9cfc8ea2dfe5a28973b461fd956b4045f24d3: pinned version.
  • At the inspected T3 commit, the schema generator pins schema-v2.0.0-alpha.3: generator source.

Both negotiate protocolVersion: 2, which does not distinguish these draft revisions. The newer contract reports failures after prompt insertion through an idle state update with stopReason: "error" and error details; T3's inspected idle schema has no error field.

This report therefore documents a compatibility gap when T3 interacts with an agent implementing the newer draft. The expected behavior and suggested tests below describe support for that newer contract. Maintainer triage should account for the draft-version mismatch when deciding whether to update the schema and handling or explicitly constrain supported revisions.

Steps to reproduce

  1. Configure acplause with an invalid OpenRouter API key.
  2. Open a session and select an available model.
  3. Send a prompt.
  4. Observe that T3 records the turn as completed without displaying the authentication error.

Protocol evidence

A separate diagnostic run of acplause using the same provider configuration accepted the prompt, then emitted this update:

{
  "sessionUpdate": "state_update",
  "state": "idle",
  "stopReason": "error",
  "error": {
    "code": -32000,
    "message": "Missing Authentication header",
    "data": {
      "kind": "auth",
      "status": 401
    }
  }
}

This is the params.update payload of a session/update notification. It is structured protocol data, not an error inferred from assistant text.

Expected behavior

The turn is marked failed and the agent's error message is displayed.

Actual behavior

The turn is marked completed and the error message is lost.

Relevant code

All links below refer to the inspected T3 commit:

Suggested regression coverage

A mock ACP v2 peer can accept the prompt and then emit the update above, without real credentials or API calls.

Assert that:

  • The turn fails and retains the original error message.
  • stopReason: "error" without error details still fails with a fallback message.
  • Normal completion and cancellation retain their existing behavior.
  • The turn is finalized exactly once.

This mock regression test has not yet been implemented.

Related reports

Activity

  1. juliusmarminge commented on Oct 18, 2026

    @juliusmarminge
    Member

    Note

    Grok responding on behalf of Julius.

    Confirmed on main. This is a bug in our ACP v2 client, not an acplause extension.

    The ACP v2 schema now defines error as a stop reason. An idle state_update with stopReason: "error" can carry an optional error field holding a JSON-RPC error object (ErrorStopReason), and the spec says that after insertion the session/prompt response stays successful, so the idle update is the only place the failure shows up. Upstream added this in agentclientprotocol/agent-client-protocol#2281 and it shipped in schema 2.0.0-alpha.8. The payload in this report matches that shape.

    T3 still generates its v2 types from schema 2.0.0-alpha.3, which predates it:

    • packages/effect-acp/scripts/generate.ts pins schema-v2.0.0-alpha.3, and the idle variant in packages/effect-acp/src/_generated/schema.gen.ts has only stopReason, usage and _meta.
    • packages/effect-acp/src/client.ts resolves the prompt completion with only those three fields, so error is dropped.
    • packages/provider-acp/src/server/adapter.ts maps every stop reason other than cancelled to completed, so "error" finishes the turn as a success.

    #14487 fixed the same symptom only for Grok's own completion extension, so it doesn't cover generic ACP agents. #20097 was a different case with no structured failure.

    Fixing it means understanding the newer stop reason and mapping error to a failed turn that keeps error.message, with a fallback message when no details are sent. The mock peer test outlined in the report covers the right cases.

  2. added
    acceptedfeature request accepted
    bugSomething is broken or behaving incorrectly.
    via-triageFiled through npx t3 triage
    on Oct 18, 2026
  3. juliusmarminge commented on Oct 22, 2026

    @juliusmarminge
    Member

    Reproduced on current main b5a226e49c: Linux web dev client with an isolated server and acplause v0.202610.0, OpenAI GPT-4.1 Mini, deliberately invalid OpenRouter key. A fresh thread and a provider-handoff thread both persisted the ACP turn as completed without displaying the error. A separate acplause stdio run accepted the prompt and emitted the reported idle error with HTTP 401. Sanitized runtime transcript. This is a protocol/error-reporting bug; no exploitable vulnerability is reported.

  4. juliusmarminge commented on Oct 22, 2026

    @juliusmarminge
    Member

    Fixed in #23218, mirrored as #23217. An accepted ACP v2 idle error now fails the turn and retains its message or a fallback. Existing command lifecycles remain unchanged. Focused registry and replay-fixture tests pass 31/31; two fresh reviews returned MERGE. Final proof audit. The PR is unmerged.

  5. travelvector commented on Oct 26, 2026

    @travelvector
    Contributor

    This symptom is also occurring with Kimi Code 2.3.1 on Windows x64, using the local ACP command kimi acp, model kimi-code/k3.1-pro, in T3 Code 0.1.3-nightly.20261026.3726 (version read from the running server).

    Although this issue is closed, #23218 is currently still open and unmerged. Could this issue remain open until the fix lands, and could the Kimi path be checked against it?

    Observed behavior

    An existing, previously working Kimi conversation encountered a TLS connection failure while refreshing OAuth credentials at https://auth.kimi.com/api/oauth/token. The current turn stopped. Two subsequent user messages each finished after approximately 18 seconds with no assistant response and no visible error. T3 persisted both runs as completed.

    Correlated evidence

    All timestamps below are UTC on 2026-10-26:

    User prompt T3 requestedAt T3 completedAt T3 status Kimi outcome
    Short acknowledgement 22:04:49.569 22:05:08.171 completed failed
    Follow-up asking why there was no response 22:05:35.223 22:05:53.372 completed failed

    Kimi's own session agents/main/wire.jsonl contains:

    {"turnId":2,"outcome":"failed","errorMessage":"OAuthConnectionError: OAuth request to https://auth.kimi.com/api/oauth/token failed: fetch failed: Client network socket disconnected before secure TLS connection was established","type":"agent.turn.ended","time":1793052308148,"kind":"event"}

    It also records turn.ended with reason: "failed", an error named OAuthConnectionError, and prompt.completed with reason: "failed". The next prompt has the same failure at 1793052353352.

    By contrast, T3's t3_thread_read reported status: "completed" for both runs. Their timeline contained the user messages and empty checkpoints, with no assistant message or failure item. The thread had no active run or pending request.

    These are Kimi's internal session events and T3's persisted run/timeline data; I did not capture the ACP stdio frames, so I cannot yet confirm that Kimi emits the exact idle state_update payload shown in the original report. This is additional evidence of the same user-visible symptom, rather than proof of an identical protocol cause.

    Recovery and expected behavior

    After connectivity recovered, a single constrained test in the same T3 thread and existing Kimi process successfully returned a short response at 22:10:23 UTC. No restart, re-login, or configuration change was needed.

    T3 should expose the provider failure and mark the run failed, so the user can diagnose/retry it. A transient authentication-network failure should not appear as a successful empty response. Restoring connectivity resolved this occurrence, but did not address the missing error reporting.

    No full conversation, credentials, or unrelated workspace contents are included here.

  6. antonov256 commented on Oct 29, 2026

    @antonov256

    Same symptom, different trigger: Kimi Code 2.3.3 via kimi acp, T3 Code server 0.1.2 (linux x64), model kimi-code/kimi-for-coding.

    In our case the provider hit its 5-hour usage limit. Outside ACP, kimi -p prints the error clearly:

    error: failed to run prompt: provider.api_error: 429 You've reached your 5-hour usage limit.
    Your quota will reset when the current 5-hour window ends.
    

    Through kimi acp, T3's session/prompt returns successfully in ~2-4 s, the turn is persisted as completed, and no turn items are produced (no reasoning, no assistant message) — the chat shows silence with no error. Reproduced on 3 consecutive prompts (01:45, 01:59, 02:38 UTC, 2026-10-29); the only earlier turn in the same session that did produce output was before the quota was exhausted.

    Server-side statev2.sqlite confirms the affected runs persisted only user_message + checkpoint items with no provider items, and the ACP registry log shows each session/prompt as succeeded.

    So this is a second trigger for the same failure class: plain API errors after prompt acceptance (429 usage limit), not only OAuth/TLS failures during credential refresh. Would be good if #23218 covers this path as well.

  7. jamesdchen commented on Nov 3, 2026

    @jamesdchen

    Still reproducing on T3 Code server 0.1.6 (linux x64) with Kimi Code 2.4.1 through kimi acp, model kimi-code/k3.1-pro. Same trigger as the 5-hour-limit report above. I saw it on about 10 threads in one evening, each started with delegate_task on a linked environment.

    What T3 shows

    • The thread and run end as completed, about 4 seconds after the run starts.
    • The only timeline items are the workspace preparation and a checkpoint: no assistant message and no error.
    • The parent's task result reads (The thread in the linked environment ended without a reply.)
    • orchestrator_capabilities keeps reporting canRunChildTask: true for the provider, so the next delegate fails the same way.

    What the logs show for the same run

    T3's provider event log has session/prompt going from started to succeeded about 3.9 s later, with a one-field result.

    Kimi's own session log (~/.kimi-code/sessions/.../agents/main/wire.jsonl) has the real failure:

    {"type":"turn.ended","reason":"failed","error":{"code":"provider.api_error","message":"429 You've reached your 5-hour usage limit. Your quota will reset when the current 5-hour window ends. ...","name":"APIProviderQuotaExhaustedError","details":{"statusCode":429},"retryable":false}}
    {"type":"prompt.completed","reason":"failed"}
    

    So on this path the failure doesn't reach T3 as a session/prompt error. I didn't capture the raw session/prompt response or its stopReason, only the logged event, so I can't say which part of the fix in #23218 this path misses. It may be a stable-ACP path rather than the v2 idle-error one. #28658 looks like the same class on Antigravity.

    Why it matters for delegation: a coordinator can't tell "finished with nothing to say" from "never ran". I started six delegates that did nothing, then a further few, and only found the cause by reading Kimi's session log on the remote machine.

    To reproduce: with a Kimi account at its 5-hour limit, start any Kimi thread or delegate_task. Expected: a failed turn showing the provider's message, like the "Usage limit reached" state Claude and Codex threads get. I can share more of the redacted logs if useful.

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

    acceptedfeature request acceptedbugSomething is broken or behaving incorrectly.via-triageFiled through npx t3 triage

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions