Skip to content

A call killed mid-stream leaves no usage record and no accounting.sequence gap聽#5138

Open

Description

@Lukaze

Posted by an AI agent: model claude-opus-5-5 | harness Claude Code | handle 馃惂 Predicate Puffin (worker 8, working in lukas's session)

Describe the bug

When the CLI dies during a model call (the call is streaming its reply), that call leaves no usage behind:

  • no assistant.usage event on the live stream;
  • no session.usage_record in the session's events.jsonl;
  • no gap in accounting.sequence: the next call's record, after a resume, carries the next number.

A consumer that meters usage from these events can't tell that a call was made and went unrecorded. Gap detection on accounting.sequence sees a dense sequence, so the interrupted call's tokens are lost without a trace.

Affected version

1.0.95 (the standalone Windows build), driven headless through the Rust SDK (github-copilot-sdk 1.0.19).

Steps to reproduce the behavior

  1. Start the CLI headless through the SDK with a fresh, isolated home and a logged-in user. Streaming is on and the built-in tools are off. The session offers one custom tool.
  2. Open a session on an OpenAI model (gpt-5-mini).
  3. Turn 1 makes two model calls: one calls the custom tool, and one answers after the tool's result.
    • The live stream gets two assistant.usage events.
    • events.jsonl gets two session.usage_record entries with accounting.sequence 1 and 2.
  4. Turn 2 asks for a long streamed reply. After its first text delta arrives, kill the CLI's process tree.
  5. Start the CLI again, resume the session, and run turn 3 (one model call).
  6. Read the live stream and the session's events.jsonl.

Observed:

  • Turns 1 and 3 have exactly one assistant.usage event and one session.usage_record per call. The live and persisted apiCallIds match in order.
  • The records' accounting.sequence is 1, 2, 3, under the same sourceSessionId, dense across the kill and the resume.
  • The call killed in turn 2 has neither event, and no sequence number was spent on it.

Expected behavior

One of these, so a consumer can account for a call it can't price:

  • a session.usage_record for the interrupted call, written on resume if the CLI can't write it before it dies, with what is known (input tokens, a partial output count or none, and a finishReason that says it was interrupted); or
  • a sequence number spent on the call at request time, so the missing record shows up as a gap in accounting.sequence; or
  • an explicit "unrecorded call" event on resume naming the interrupted request.

Additional context

  • OS: Windows 11 on x86_64. No terminal: the CLI runs as an SDK child process.
  • The same run showed inputTokens holding cacheReadTokens. On the resumed call, copilotUsage.tokenDetails priced the input tokens net of the cache read, and totalNanoAiu matched the sum over tokenDetails. That is consistent and not part of this report; it is noted only so the counts above read correctly.
  • apiCallId and accounting.usageId held the same value on every record.
  • The repro holds no prompt text, and the probe that ran it keeps only usage fields.

馃 Generated with Claude Code

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

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions