authorgravatar for i.kosilov2003@gmail.comVantrongs <i.kosilov2003@gmail.com> 2026-10-11 09:41:30+03:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-10-11 01:41:30-05:00
log119e61b6c1ee642fa48b4447812fb77f17b0bfe5
tree51a88e69d93ec38c24253213510458f5a2ee817a
parent8dfee385721d5a0d613803da430e7b2276fadec5
signature Signed by PGP key B5690EEEBB952194

fix(server): a Claude usage limit no longer resets the context meter to 0% (#16394)

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Co-authored-by: Yash Singh <saiansh2525@gmail.com>

2 files changed, 75 insertions(+), 1 deletions(-)

apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.test.ts+68
......@@ -3355,6 +3355,74 @@ describe("ClaudeAdapterV2 background wake turns", () => {
33553355 ),
33563356 );
33573357
3358 it.effect("keeps the measured context when a usage limit ends the turn", () =>
3359 Effect.gen(function* () {
3360 const harness = yield* makeWakeHarness;
3361 yield* harness.runtime.startTurn(
3362 makeClaudeTestTurnInput({
3363 threadId: harness.threadId,
3364 providerThread: harness.providerThread,
3365 now: yield* DateTime.now,
3366 attemptId: RunAttemptId.make("attempt-limit-context"),
3367 text: "Continue.",
3368 attachments: [],
3369 }),
3370 );
3371 yield* Queue.offer(
3372 harness.sdkMessages,
3373 makeAssistantTextFrame({ uuid: "00000000-0000-4000-8000-000000000660", text: "Working." }),
3374 );
3375 // The CLI answers a 429 with a locally generated frame that reports no usage.
3376 yield* Queue.offer(
3377 harness.sdkMessages,
3378 claudeSdkFrame({
3379 type: "assistant",
3380 message: {
3381 model: "<synthetic>",
3382 id: "00000000-0000-4000-8000-000000000661",
3383 type: "message",
3384 role: "assistant",
3385 content: [{ type: "text", text: "You've hit your session limit" }],
3386 stop_reason: "stop_sequence",
3387 stop_sequence: "",
3388 usage: {
3389 input_tokens: 0,
3390 output_tokens: 0,
3391 cache_creation_input_tokens: 0,
3392 cache_read_input_tokens: 0,
3393 },
3394 },
3395 parent_tool_use_id: null,
3396 error: "rate_limit",
3397 uuid: "00000000-0000-4000-8000-000000000661",
3398 session_id: WAKE_NATIVE_SESSION,
3399 }),
3400 );
3401 yield* Queue.offer(
3402 harness.sdkMessages,
3403 makeResultFrame({
3404 uuid: "00000000-0000-4000-8000-000000000662",
3405 result: "You've hit your session limit",
3406 terminalReason: "api_error",
3407 isError: true,
3408 apiErrorStatus: 429,
3409 }),
3410 );
3411 const terminal = yield* Queue.take(harness.terminalReceipts);
3412 assert.equal(terminal.status, "failed");
3413 const reportedContext = harness.events.flatMap((event) =>
3414 event.type === "provider_turn.updated" && event.providerTurn.tokenUsage
3415 ? [event.providerTurn.tokenUsage.usedTokens]
3416 : [],
3417 );
3418 assert.deepEqual(reportedContext, [2]);
3419 }).pipe(
3420 Effect.provide(
3421 Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer),
3422 ),
3423 ),
3424 );
3425
33583426 it.effect("surfaces a Claude safety model fallback without failing the turn", () =>
33593427 Effect.gen(function* () {
33603428 const harness = yield* makeWakeHarness;
apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.ts+7-1
......@@ -6933,7 +6933,13 @@ export const makeClaudeAdapterV2 = Effect.fn("makeClaudeAdapterV2")(function* (
69336933 if (message.type === "assistant" && input.replayed !== true) {
69346934 const now = yield* DateTime.now;
69356935 yield* completeProviderRetry(context, now);
6936 if (message.parent_tool_use_id === null && message.message.usage !== undefined) {
6936 // `<synthetic>` frames (usage limits, sign-in errors, /goal status)
6937 // are generated by the CLI and report zero usage, not the context.
6938 if (
6939 message.parent_tool_use_id === null &&
6940 message.message.usage !== undefined &&
6941 message.message.model !== "<synthetic>"
6942 ) {
69376943 yield* emitProviderEvent({
69386944 type: "provider_turn.updated",
69396945 driver: CLAUDE_PROVIDER,