fix(weekly-digest): recover from adaptive-thinking max_tokens exhaustion - #21213
fix(weekly-digest): recover from adaptive-thinking max_tokens exhaustion#21213workprentice[bot] wants to merge 1 commit into
Conversation
The Synthesize step calls claude-sonnet-5 with thinking: adaptive and no explicit effort, which defaults to Anthropic's most reasoning-heavy tier. Thinking tokens count against max_tokens, and the digest this formats grows every week, so a fixed 16000-token budget that worked at launch eventually stops leaving room for the response text: - 2026-08-17 (run 32037994970): the model was cut off mid-JSON -> invalid JSON -> 'Synthesis output was not valid JSON'. - 2026-08-24 (run 32737641009): thinking alone consumed the whole budget, leaving zero text blocks -> 'Anthropic API returned no text'. The diagnostic head -c 2000 dump was entirely swallowed by the thinking block's base64 signature, hiding stop_reason/usage from the log. Both are the documented stop_reason: max_tokens failure mode (see https://platform.claude.com/docs/en/build-with-claude/thinking-troubleshooting#the-response-stops-with-stop_reason-max_tokens). - Set output_config.effort: low. This call formats data the COLLECT step already gathered; it is not the complex-reasoning workload effort defaults to, and low effort is Anthropic's own guidance for 'chat and non-coding use cases'. - Raise the initial budget to 24000 and add one bounded retry at 32000 if the first attempt reports stop_reason: max_tokens, so one unlucky week does not silently skip the digest. - Replace the raw response-body dump with a jq summary of stop_reason, usage, error, content block types, and a text preview -- fields that can no longer be pushed out by an opaque thinking signature. Verified with a local harness (curl stubbed) against six fixtures: clean success, max-tokens-then-retry-succeeds, curl failure, fenced JSON, missing keys, and max-tokens-persists-after-retry (the diagnosability regression guard, confirming stop_reason/usage stay visible past a 3000-char signature). Left the structurally similar .content[0].text extraction in claude-triage.yml and auto-label-issues.yml alone: neither enables thinking and both use haiku with small max_tokens, so they are not exposed to this failure mode today, and claude-triage.yml already has an open PR (#21095) touching it. Fixes #21086
Pre-merge Review — Last updated 2026-08-28T14:17:11ZTip Summary: This is an infrastructure change to a single GitHub Actions workflow ( Review confidence:
Investigation log
🔍 Verification trail7 claims extracted · 3 verified · 0 unverifiable · 0 contradicted
🚨 Outstanding in this PRNo outstanding findings in this PR.
|
|
Your site preview for commit 03bdda6 is ready! 🎉 http://www-testing-pulumi-docs-origin-pr-21213-03bdda69.s3-website.us-west-2.amazonaws.com |
What broke
The
Synthesize digestsstep inweekly-digest.ymlcallsclaude-sonnet-5withthinking: {type: "adaptive"}andmax_tokens: 16000, with no expliciteffort, which defaults to Anthropic's most reasoning-heavy tier (high). Thinking tokens count againstmax_tokens, and the backlog this step formats only grows week over week, so the fixed budget that worked when the workflow launched eventually stopped leaving room for the response text. Two consecutive Monday runs failed:Synthesis output was not valid JSON.Anthropic API returned no text. The diagnostichead -c 2000dump was entirely swallowed by the thinking block's base64signaturefield, hidingstop_reasonandusage— the two fields that would have explained this immediately — from whoever read the log.Both failures are the documented
stop_reason: "max_tokens"case: Troubleshooting thinking § "The response stops with stop_reason: max_tokens".What changed
All in the
Synthesize digestsstep, no other workflow behavior touched:output_config: {effort: "low"}. This call formats/organizes data the deterministicCollect digest datastep already gathered — it isn't the complex-reasoning workloadeffortdefaults to. Anthropic's own guidance recommends low effort for exactly this: "chat and non-coding use cases where faster turnaround is prioritized." Lower effort means less of the budget goes to thinking in the first place.stop_reasonis"max_tokens". Both observed failures were this exact stop reason, so a single retry with real headroom covers a bad week without an unbounded loop.jqsummary —{stop_reason, usage, error, content_block_types, text_preview}— instead of ahead -c 2000of the whole JSON body. A thinking block'ssignaturealone can run past a couple thousand characters, which is exactly what hid the diagnosis on 2026-08-24; this summary explicitly excludessignatureand the rawthinkingtext so the fields that actually explain a failure can't be pushed out again.type=="text"rather thancontent[0]), fence stripping, and key validation intact.Verification
No Anthropic API key is available to me, so I couldn't exercise this against the live API. Instead:
gh run view --log-failed) and confirmed the dumped response bodies match thestop_reason: "max_tokens"diagnosis above.call_anthropic'scurlreplaced by a stub that serves synthetic fixture responses, and ran it against six cases: clean success; max-tokens-on-attempt-1-then-retry-succeeds;curlfailure; text wrapped in```jsonfences; valid JSON missingbacklog_digest; and max-tokens-persists-even-after-retry — the last one is the regression guard for the diagnosability fix, confirmingstop_reason/usagestay visible in the summary past a 3000-character signature. All six behaved as expected.bash -non the extracted script andpython3 -c "import yaml; yaml.safe_load(...)"on the whole workflow file both pass.git diff --stat— one file touched, the intended one.Scope note
claude-triage.ymlandauto-label-issues.ymlalso extract text with the older.content[0].textpattern, which is fragile in the same general way. I left both alone: neither enablesthinking, and both useclaude-haiku-4-5with smallmax_tokens(512 / 200), so they aren't exposed to this specific failure mode today.claude-triage.ymlalso has an open PR (#21095) already touching it, so I'd rather not add a second cook there.🧠 This PR was created by workprentice.