Back to blog
Aug 27
dogfooding
byok
anthropic
provider-reliability
engineering-log

Run #11: The Call Landed, and Got Rejected Without a Reason

Run #10 closed with the platform-duration and acquisition-latency problems both genuinely fixed, and one thing still unexplained: Builder's Anthropic call kept aborting at exactly the application's own 60-second timeout, twice, with nothing else left to blame it on. Before running it again, we gave Builder — and only Builder — more time, on the strength of evidence, not a guess.

What we did manually, before this run

  • Confirmed the 60-second ceiling was Builder's actual bottleneck, not a side effect of anything else. Run #10's own numbers: Builder's input volume was ordinary (20 selected files, identical to every other role), acquisition was down to ~9 seconds, and the remaining ~60-73 seconds of each attempt was the Anthropic call itself, aborting right at the configured ceiling.
  • Raised the timeout for Builder specifically — not globally. Every other role kept the existing 60-second timeout; only Builder's calls now get 120 seconds. Nothing else changed: not maxAttempts, not retry backoff, not the cross-invocation retry-deferral behavior, not the prompt, not the output-token ceiling.
  • Did the headroom arithmetic before shipping it, the same discipline that caught Run #8's premature 120-second timeout: measured pre-provider work (~11s) plus the new 120-second ceiling plus measured post-provider work (~3s) comes to 134 seconds — a 166-second margin under this repository's own documented ~300-second platform ceiling, not a number picked because it sounded reasonable.
  • Wrote the change so a future run could actually tell us if we'd guessed wrong — tests proving the resolved timeout reaches the real dispatch path, not just the function that computes it in isolation.

The task

The same request as the last five runs, deliberately unchanged:

"Improve the BinChicken run-detail page by adding a small 'Last updated' timestamp showing when the run data was most recently refreshed. Use the existing run-detail components and styling. Add appropriate tests. Do not change the workflow engine, AI providers, GitHub integration, governance, authentication, or database schema. The timestamp should appear alongside the existing 'started' timestamp and use the existing timestamp formatting and muted metadata styling."

What happened

Scope, Architecture, Security, and UX all completed cleanly and quickly — comparable to Run #10's times, nothing new here. Security's approval gate opened the same way it always does.

Builder's Anthropic call completed. For the first time across three runs. 102.7 seconds of actual call time, comfortably inside the new 120-second ceiling — no application_timeout, no abort. The response was well-formed JSON and matched the base structured-output schema. Every piece of the timeout fix worked exactly as intended.

Then it failed anyway — for a genuinely different reason. "Builder response did not contain a valid, safely-bounded structured file patch." Not a timeout. Not malformed JSON. Not a schema mismatch. A second, distinct validation layer — the one that checks a Builder's proposed file changes are safe and bounded (real paths, reasonable sizes, a real commit message) — rejected the patch after the call itself had already succeeded. The engine correctly recognized this as non-retryable and went straight to a terminal failure rather than spending a second attempt on a response likely to come back shaped the same way.

We didn't know which of nine possible reasons that was, and neither could anyone else. Before touching anything, we went and checked, read-only, against the run's own durable data: the response wasn't truncated (ruled out two independent ways — the code path that would have caught a token-ceiling cutoff never fired, and the actual token count landed short of the ceiling anyway), input volume was ordinary, and the specific validation failure — an unsafe path, a file too large, too many files, something else entirely — was gone. By design: the module that runs this check deliberately discards the specific reason before it can ever reach a log line or an error message, so a genuinely relevant path or file content could never leak through as a side effect. That discipline is correct. Losing the category of failure along with it wasn't a deliberate tradeoff — nobody had needed to distinguish "file too large" from "unsafe path" until this run made it the only open question left.

What we built in response, before considering another run

A safe, typed reason travels with the failure now — which of the ten specific things a Builder patch can fail for, plus plain numbers where relevant (how many files were proposed against the limit, how large a file was against its ceiling) — never a path, never a scrap of the model's actual content. It rides inside the same message that was already being durably recorded, so nothing new has to be persisted anywhere to see it. Two smaller gaps came out of the same look: Anthropic's own request id was being dropped for this one failure category specifically, unlike every sibling category that already keeps it, and a database column meant to link a usage record back to the exact execution it billed had existed since this project's first migration without anything ever writing to it. Both fixed. None of it touches what got validated or how — the validation limits, the prompt, the retry policy, and the new 120-second timeout are exactly what they were before this pass.

The part worth not burying under the good news

Watching this run live surfaced a different kind of gap, the same category Run #9 flagged and still unresolved: showing someone the entire context file at an approval gate does not tell them what they're actually being asked to approve. It's complete, and it's the opposite of legible. That's a UI problem, not a data problem, and it's still open.

Where this leaves things

  • The platform-duration problem, the acquisition-latency problem, and the 60-second-timeout problem are all confirmed fixed — three runs, three separate root causes, three separate fixes, each verified against a real recurrence rather than assumed from a patch's own design.
  • Builder's actual output is the open question now, not its execution shape. Whatever the model returned failed one specific, currently-unknown safety check. The next occurrence will say exactly which one.
  • The approval-gate legibility gap is still just flagged, not fixed — worth its own attention, not folded into whatever comes next by default.
  • No run is planned until a builder_patch_invalid failure actually happens again with the new diagnostics in place — which needs another real attempt, not a guess about which of nine reasons is most likely.