Skip to content

fix: ensure usage records are published for 0-token responses#2272

Open
LucasInsight wants to merge 1 commit intorouter-for-me:mainfrom
LucasInsight:fix-zero-token-usage
Open

fix: ensure usage records are published for 0-token responses#2272
LucasInsight wants to merge 1 commit intorouter-for-me:mainfrom
LucasInsight:fix-zero-token-usage

Conversation

@LucasInsight
Copy link
Copy Markdown

@LucasInsight LucasInsight commented Mar 22, 2026

When an upstream model returns 0 tokens, CLIProxyAPI's publishWithOutcome correctly drops the chunk to prevent empty streaming spam. However, several executors were missing the defer reporter.ensurePublished(ctx) safety call at the end of their execution paths to guarantee that the request itself is logged exactly once before closing the connection. This PR patches all missing executors to ensure 0-usage responses still record the transaction cleanly.

Resolves #2273

@gemini-code-assist
Copy link
Copy Markdown
Contributor

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request addresses an issue where usage records were not being published for API responses that returned zero tokens. It resolves this by adding a deferred call to reporter.ensurePublished(ctx) in various executor functions, guaranteeing that every transaction is logged once, regardless of the token count, thereby improving the accuracy and completeness of usage reporting.

Highlights

  • Usage Reporting: Ensured that usage records are consistently published for all API responses, including those with zero tokens, by adding a defer reporter.ensurePublished(ctx) call.
  • Executor Consistency: Applied the fix across multiple executor implementations (AIStudio, Antigravity, Claude, Codex, CodexWebsockets, GeminiCLI, Gemini, GeminiVertex, IFlow, Kimi, Qwen) to prevent missed usage logging.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 4041909029

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines 119 to +120
defer reporter.trackFailure(ctx, &err)
defer reporter.ensurePublished(ctx)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Defer failure tracking after ensurePublished

In Execute, this defer order is backwards for Go's LIFO semantics: ensurePublished runs first, calls once.Do, and then trackFailure cannot publish a failed record anymore. That means any error path that returns before an explicit usage publish (for example translateRequest failures in this function) is now recorded as a successful zero-usage request, which corrupts failure accounting. The same pattern appears in the other executors changed in this commit.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request aims to fix an issue where usage records are not published for 0-token responses by adding defer reporter.ensurePublished(ctx) to several executors. While this change correctly identifies the need for ensurePublished, the order of the deferred calls introduces a new issue. Due to Go's LIFO execution of defer statements, the current implementation will prevent failure tracking. I've left comments with suggestions to reorder the defer statements to ensure both success and failure paths are handled correctly.

Comment on lines 119 to +120
defer reporter.trackFailure(ctx, &err)
defer reporter.ensurePublished(ctx)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The order of these defer statements is important. In Go, defer statements are executed in a Last-In, First-Out (LIFO) order. With the current order, reporter.ensurePublished(ctx) will be executed before reporter.trackFailure(ctx, &err).

Both ensurePublished and trackFailure use sync.Once to ensure the usage record is published only once. This means if an error occurs, ensurePublished will run first and publish a success record. trackFailure will then run, but its attempt to publish a failure record will be a no-op.

To fix this, the defer statements should be reordered so that trackFailure is executed first.

Suggested change
defer reporter.trackFailure(ctx, &err)
defer reporter.ensurePublished(ctx)
defer reporter.ensurePublished(ctx)
defer reporter.trackFailure(ctx, &err)

Comment on lines 179 to +180
defer reporter.trackFailure(ctx, &err)
defer reporter.ensurePublished(ctx)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The order of these defer statements is important. In Go, defer statements are executed in a Last-In, First-Out (LIFO) order. With the current order, reporter.ensurePublished(ctx) will be executed before reporter.trackFailure(ctx, &err).

Both ensurePublished and trackFailure use sync.Once to ensure the usage record is published only once. This means if an error occurs, ensurePublished will run first and publish a success record. trackFailure will then run, but its attempt to publish a failure record will be a no-op.

To fix this, the defer statements should be reordered so that trackFailure is executed first.

Suggested change
defer reporter.trackFailure(ctx, &err)
defer reporter.ensurePublished(ctx)
defer reporter.ensurePublished(ctx)
defer reporter.trackFailure(ctx, &err)

Comment on lines 207 to +208
defer reporter.trackFailure(ctx, &err)
defer reporter.ensurePublished(ctx)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The order of these defer statements is important. In Go, defer statements are executed in a Last-In, First-Out (LIFO) order. With the current order, reporter.ensurePublished(ctx) will be executed before reporter.trackFailure(ctx, &err).

Both ensurePublished and trackFailure use sync.Once to ensure the usage record is published only once. This means if an error occurs, ensurePublished will run first and publish a success record. trackFailure will then run, but its attempt to publish a failure record will be a no-op.

To fix this, the defer statements should be reordered so that trackFailure is executed first.

Suggested change
defer reporter.trackFailure(ctx, &err)
defer reporter.ensurePublished(ctx)
defer reporter.ensurePublished(ctx)
defer reporter.trackFailure(ctx, &err)

Comment on lines 350 to +351
defer reporter.trackFailure(ctx, &err)
defer reporter.ensurePublished(ctx)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The order of these defer statements is important. In Go, defer statements are executed in a Last-In, First-Out (LIFO) order. With the current order, reporter.ensurePublished(ctx) will be executed before reporter.trackFailure(ctx, &err).

Both ensurePublished and trackFailure use sync.Once to ensure the usage record is published only once. This means if an error occurs, ensurePublished will run first and publish a success record. trackFailure will then run, but its attempt to publish a failure record will be a no-op.

To fix this, the defer statements should be reordered so that trackFailure is executed first.

Suggested change
defer reporter.trackFailure(ctx, &err)
defer reporter.ensurePublished(ctx)
defer reporter.ensurePublished(ctx)
defer reporter.trackFailure(ctx, &err)

Comment on lines 754 to +755
defer reporter.trackFailure(ctx, &err)
defer reporter.ensurePublished(ctx)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The order of these defer statements is important. In Go, defer statements are executed in a Last-In, First-Out (LIFO) order. With the current order, reporter.ensurePublished(ctx) will be executed before reporter.trackFailure(ctx, &err).

Both ensurePublished and trackFailure use sync.Once to ensure the usage record is published only once. This means if an error occurs, ensurePublished will run first and publish a success record. trackFailure will then run, but its attempt to publish a failure record will be a no-op.

To fix this, the defer statements should be reordered so that trackFailure is executed first.

Suggested change
defer reporter.trackFailure(ctx, &err)
defer reporter.ensurePublished(ctx)
defer reporter.ensurePublished(ctx)
defer reporter.trackFailure(ctx, &err)

Comment on lines 181 to +182
defer reporter.trackFailure(ctx, &err)
defer reporter.ensurePublished(ctx)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The order of these defer statements is important. In Go, defer statements are executed in a Last-In, First-Out (LIFO) order. With the current order, reporter.ensurePublished(ctx) will be executed before reporter.trackFailure(ctx, &err).

Both ensurePublished and trackFailure use sync.Once to ensure the usage record is published only once. This means if an error occurs, ensurePublished will run first and publish a success record. trackFailure will then run, but its attempt to publish a failure record will be a no-op.

To fix this, the defer statements should be reordered so that trackFailure is executed first.

Suggested change
defer reporter.trackFailure(ctx, &err)
defer reporter.ensurePublished(ctx)
defer reporter.ensurePublished(ctx)
defer reporter.trackFailure(ctx, &err)

Comment on lines 76 to +77
defer reporter.trackFailure(ctx, &err)
defer reporter.ensurePublished(ctx)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The order of these defer statements is important. In Go, defer statements are executed in a Last-In, First-Out (LIFO) order. With the current order, reporter.ensurePublished(ctx) will be executed before reporter.trackFailure(ctx, &err).

Both ensurePublished and trackFailure use sync.Once to ensure the usage record is published only once. This means if an error occurs, ensurePublished will run first and publish a success record. trackFailure will then run, but its attempt to publish a failure record will be a no-op.

To fix this, the defer statements should be reordered so that trackFailure is executed first.

Suggested change
defer reporter.trackFailure(ctx, &err)
defer reporter.ensurePublished(ctx)
defer reporter.ensurePublished(ctx)
defer reporter.trackFailure(ctx, &err)

Comment on lines 184 to +185
defer reporter.trackFailure(ctx, &err)
defer reporter.ensurePublished(ctx)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The order of these defer statements is important. In Go, defer statements are executed in a Last-In, First-Out (LIFO) order. With the current order, reporter.ensurePublished(ctx) will be executed before reporter.trackFailure(ctx, &err).

Both ensurePublished and trackFailure use sync.Once to ensure the usage record is published only once. This means if an error occurs, ensurePublished will run first and publish a success record. trackFailure will then run, but its attempt to publish a failure record will be a no-op.

To fix this, the defer statements should be reordered so that trackFailure is executed first.

Suggested change
defer reporter.trackFailure(ctx, &err)
defer reporter.ensurePublished(ctx)
defer reporter.ensurePublished(ctx)
defer reporter.trackFailure(ctx, &err)

Comment on lines 232 to +233
defer reporter.trackFailure(ctx, &err)
defer reporter.ensurePublished(ctx)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The order of these defer statements is important. In Go, defer statements are executed in a Last-In, First-Out (LIFO) order. With the current order, reporter.ensurePublished(ctx) will be executed before reporter.trackFailure(ctx, &err).

Both ensurePublished and trackFailure use sync.Once to ensure the usage record is published only once. This means if an error occurs, ensurePublished will run first and publish a success record. trackFailure will then run, but its attempt to publish a failure record will be a no-op.

To fix this, the defer statements should be reordered so that trackFailure is executed first.

Suggested change
defer reporter.trackFailure(ctx, &err)
defer reporter.ensurePublished(ctx)
defer reporter.ensurePublished(ctx)
defer reporter.trackFailure(ctx, &err)

Comment on lines 336 to +337
defer reporter.trackFailure(ctx, &err)
defer reporter.ensurePublished(ctx)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The order of these defer statements is important. In Go, defer statements are executed in a Last-In, First-Out (LIFO) order. With the current order, reporter.ensurePublished(ctx) will be executed before reporter.trackFailure(ctx, &err).

Both ensurePublished and trackFailure use sync.Once to ensure the usage record is published only once. This means if an error occurs, ensurePublished will run first and publish a success record. trackFailure will then run, but its attempt to publish a failure record will be a no-op.

To fix this, the defer statements should be reordered so that trackFailure is executed first.

Suggested change
defer reporter.trackFailure(ctx, &err)
defer reporter.ensurePublished(ctx)
defer reporter.ensurePublished(ctx)
defer reporter.trackFailure(ctx, &err)

Copy link
Copy Markdown
Collaborator

@luispater luispater left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Summary:
This PR adds a safety ensurePublished(ctx) fallback across executors so 0-token / missing-usage upstream responses still emit a single usage record. Good direction, and CI looks green.

Blocking:

  • The defer order is currently:
    • defer reporter.trackFailure(ctx, &err)
    • defer reporter.ensurePublished(ctx)
      In Go, defers run LIFO. Since usageReporter uses sync.Once, ensurePublished will run first and publish a success record (Failed=false), which can prevent trackFailure from ever publishing a failure record (Failed=true) when err != nil.
      Please swap the order everywhere to:
    • defer reporter.ensurePublished(ctx)
    • defer reporter.trackFailure(ctx, &err)

Non-blocking:

  • Consider a small helper to register these defers consistently to avoid reintroducing the ordering bug.

Test plan:

  • CI: pr-test-build (passed)
  • Recommended locally: go test ./...

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Usage statistics not recorded when upstream returns 0 tokens

2 participants