Skip to content

fix(weekly-digest): recover from adaptive-thinking max_tokens exhaustion - #21213

Open
workprentice[bot] wants to merge 1 commit into
masterfrom
fix_issue_21086_weekly_digest_synthesis
Open

fix(weekly-digest): recover from adaptive-thinking max_tokens exhaustion#21213
workprentice[bot] wants to merge 1 commit into
masterfrom
fix_issue_21086_weekly_digest_synthesis

Conversation

@workprentice

@workprentice workprentice Bot commented Aug 28, 2026

Copy link
Copy Markdown
Contributor

What broke

The Synthesize digests step in weekly-digest.yml calls claude-sonnet-5 with thinking: {type: "adaptive"} and max_tokens: 16000, with no explicit effort, which defaults to Anthropic's most reasoning-heavy tier (high). Thinking tokens count against max_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:

  • 2026-08-17 (run 32037994970): the model was cut off partway through writing the JSON, producing incomplete text → Synthesis output was not valid JSON.
  • 2026-08-24 (run 32737641009): thinking alone consumed the entire 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 field, hiding stop_reason and usage — 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 digests step, no other workflow behavior touched:

  1. output_config: {effort: "low"}. This call formats/organizes data the deterministic Collect digest data step already gathered — it isn't the complex-reasoning workload effort defaults 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.
  2. Raised the initial budget from 16000 to 24000, plus one bounded retry at 32000 if the first attempt's stop_reason is "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.
  3. Replaced the raw response-body dump with a jq summary{stop_reason, usage, error, content_block_types, text_preview} — instead of a head -c 2000 of the whole JSON body. A thinking block's signature alone can run past a couple thousand characters, which is exactly what hid the diagnosis on 2026-08-24; this summary explicitly excludes signature and the raw thinking text so the fields that actually explain a failure can't be pushed out again.
  4. Kept the existing text-block selection (already correct — it already selected by type=="text" rather than content[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:

  • Pulled both failing run logs (gh run view --log-failed) and confirmed the dumped response bodies match the stop_reason: "max_tokens" diagnosis above.
  • Extracted the step's shell logic into a standalone harness with call_anthropic's curl replaced by a stub that serves synthetic fixture responses, and ran it against six cases: clean success; max-tokens-on-attempt-1-then-retry-succeeds; curl failure; text wrapped in ```json fences; valid JSON missing backlog_digest; and max-tokens-persists-even-after-retry — the last one is the regression guard for the diagnosability fix, confirming stop_reason/usage stay visible in the summary past a 3000-character signature. All six behaved as expected.
  • bash -n on the extracted script and python3 -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.yml and auto-label-issues.yml also extract text with the older .content[0].text pattern, which is fragile in the same general way. I left both alone: neither enables thinking, and both use claude-haiku-4-5 with small max_tokens (512 / 200), so they aren't exposed to this specific failure mode today. claude-triage.yml also has an open PR (#21095) already touching it, so I'd rather not add a second cook there.


🧠 This PR was created by workprentice.

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
@github-actions github-actions Bot added the review:triaging Claude Triage is currently classifying the PR label Aug 28, 2026
@github-actions github-actions Bot added domain:infra PR touches workflows, scripts, infra, Makefile, or build config review:in-progress Claude review is currently running and removed review:triaging Claude Triage is currently classifying the PR labels Aug 28, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Pre-merge Review — Last updated 2026-08-28T14:17:11Z

Tip

Summary: This is an infrastructure change to a single GitHub Actions workflow (.github/workflows/weekly-digest.yml), hardening the "Synthesize digests" step against the case where adaptive thinking consumes the whole max_tokens budget and leaves no room for the digest text. It raises the initial budget from 16000 to 24000, adds output_config.effort: "low", wraps the request in a reusable call_anthropic shell function, adds one bounded retry at 32000 on stop_reason == "max_tokens", and replaces the raw head -c 2000 dump with a summarize_response helper that surfaces stop_reason/usage instead of drowning them in a thinking block's base64 signature. The wrongness that would matter here is a shell or API-shape error that makes the step fail differently — an unquoted budget, a set -euo pipefail interaction that swallows the retry, an API field the endpoint doesn't accept, or a jq filter that silently yields empty text. Passes run: infra review of the diff (shell semantics, error paths, retry bounds, secret handling), and external verification of the API endpoint, the anthropic-version header value, and the linked thinking-troubleshooting anchor. No content, frontmatter, links, or code examples are touched, so the docs-specific passes did not apply.

Review confidence:

Dimension Level Notes
mechanics HIGH
facts MEDIUM The API endpoint, anthropic-version header, and the cited troubleshooting anchor verified against Anthropic's docs. The output_config.effort request field was not independently confirmed against the published API reference — if the endpoint rejects unknown fields, that would surface as a hard failure on the next scheduled run.
infra HIGH Shell paths reviewed by hand: the retry is bounded at one attempt, both failure exits are explicit, $ANTHROPIC_API_KEY stays in a header and is never echoed by summarize_response, and the || echo '{"error":"curl_failed"}' fallback still yields parseable JSON under pipefail.
Investigation log
  • Cross-sibling reads: not run (not in a templated section)
  • External claim verification: 3 of 7 claims verified (0 unverifiable, 0 contradicted) · 4 specialists (numerical, cross-reference, capability, framing); 0 cross-specialist corroborations · routed: 0 inline, 0 Pass 1, 0 Pass 2, 7 Pass 3 (verified 3, contradicted 0, unverifiable 4).
  • Cited-claim spot-checks: not run (no cited claims)
  • Frontmatter sweep: not run (no frontmatter in diff)
  • Temporal-trigger sweep: not run (no trigger words)
  • Code execution: not run (no static/programs/ change)
  • Code-examples checks: not run (no fenced code blocks in content files)
  • Editorial-balance pass: not run (not under content/blog/)
🚨 Outstanding ⚠️ Low-confidence 💡 Pre-existing ✅ Resolved
0 0 0 0

🔍 Verification trail

7 claims extracted · 3 verified · 0 unverifiable · 0 contradicted
  • L67 in .github/workflows/weekly-digest.yml "# https://platform.claude.com/docs/en/build-with-claude/thinking-troubleshooting#the-response-stops-with-stop_reason-max_tokens)," → ✅ verified (evidence: The page platform.claude.com/docs/en/build-with-claude/thinking-troubleshooting contains a section titled with the exact anchor text describing this failure: "The response ends with stop_reason: "max_tokens", often with a truncated or…; source: https://platform.claude.com/docs/en/build-with-claude/thinking-troubleshooting#the-response-stops-with-stop_reason-max_tokens)
  • L85 in .github/workflows/weekly-digest.yml "local budget='$1'" → ➖ not-a-claim (evidence: This is a shell script line (local budget="$1") from a GitHub Actions workflow file, assigning a function's first positional argument to a local variable. It is code, not a falsifiable factual assertion.; source: .github/workflows/weekly-digest.yml (line 85))
  • L100 in .github/workflows/weekly-digest.yml "| curl -sS https://api.anthropic.com/v1/messages " → ✅ verified (evidence: The URL https://api.anthropic.com/v1/messages is confirmed as Anthropic's actual, documented Messages API endpoint: "The native endpoint is POST https://api.anthropic.com/v1/messages." The workflow file correctly uses this real endpoint…; source: https://blogs.novita.ai/anthropic-messages-api-documentation/)
  • L102 in .github/workflows/weekly-digest.yml "-H 'anthropic-version: 2023-06-01' " → ✅ verified (evidence: Anthropic's official docs confirm the required request header value: "When making API requests, you must send an anthropic-version request header. For example, anthropic-version: 2023-06-01."; source: https://platform.claude.com/docs/en/api/versioning)
  • L114 in .github/workflows/weekly-digest.yml "echo '$1' | jq -c '{" → ➖ not-a-claim (evidence: This is a line of shell/jq code from a GitHub Actions workflow file (piping JSON into jq with a filter), not a falsifiable factual assertion.; source: .github/workflows/weekly-digest.yml L114)
  • L120 in .github/workflows/weekly-digest.yml "}' 2>/dev/null || echo '$1' | head -c 500" → ➖ not-a-claim (evidence: This is a shell script line from a GitHub Actions workflow file (a fallback command using head -c 500 in case JSON parsing fails), not a falsifiable factual assertion.; source: .github/workflows/weekly-digest.yml, line 120)
  • L128 in .github/workflows/weekly-digest.yml "# mid-JSON — both observed in the wild (2026-08-24 and 2026-08-17" → ➖ not-a-claim (evidence: This is a code comment in a GitHub Actions workflow file describing the author's own observations while debugging JSON parsing behavior, not a falsifiable external claim.; source: .github/workflows/weekly-digest.yml L128)

🚨 Outstanding in this PR

No outstanding findings in this PR.

⚠️ Low-confidence

No low-confidence findings.

💡 Pre-existing issues in touched files (optional)

No pre-existing issues in touched files.

✅ Resolved since last review

No items resolved since the last review.

📜 Review history

  • 2026-08-28T14:17:11Z — Reviewed the weekly-digest workflow's max_tokens retry hardening; no blockers found — the Anthropic endpoint, version header, and cited troubleshooting anchor all verified, and the shell error paths are sound. (03bdda6)

  • Refresh this review — comment @claude #update-review. Say what you fixed, or which finding you dispute and why; both work in the same mention.
  • Ask for anything else — comment @claude with no hashtag (questions, one-off fixes). Leaves this review untouched.

Important

Please don't hide, resolve, or delete this comment! It breaks things!

📖 How pre-merge review works — the full lifecycle, short-circuits, and escape hatches.

@github-actions github-actions Bot added review:no-blockers Claude review completed cleanly; outstanding is empty and removed review:in-progress Claude review is currently running labels Aug 28, 2026
@pulumi-bot

Copy link
Copy Markdown
Collaborator

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

Labels

domain:infra PR touches workflows, scripts, infra, Makefile, or build config review:no-blockers Claude review completed cleanly; outstanding is empty

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants