This repository was archived by the owner on Aug 11, 2026. It is now read-only.
fix(relay): forward the platform's rejection detail to the harness - #125
Merged
Merged
Conversation
A pre-reservation 4xx from the platform reached the harness as the fixed
string "inference request denied", even though the platform's own
explanation was sitting in responseBody at that exact site. Combined with
the platform saying only "unsupported inference parameter" without naming
the parameter, a miner whose request carried one unrecognised field had no
channel at all to learn its name.
`Cooking` burned three submissions on a single unrecognised `reasoning`
key -- v3 shipped byte-identical to v2 on that line -- because nothing in
the stack ever said "reasoning".
platformRejectionMessage reads `message` out of the platform's error
envelope, strips control characters (this string lands in a log line and a
harness's stderr, so a newline could forge an entry), and bounds it at 400
bytes. An unparseable or empty body falls back to the old wording, so an
older platform is unaffected.
Classification is deliberately untouched: same status, same counters, same
agent_request_rejections bucket, same {"error": ...} response shape. Only
the human-readable string differs, which is what makes this safe across
the 0.34.1-0.37.3 validator fleet.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Peyton-Spencer
marked this pull request as ready for review
July 28, 2026 17:28
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to subscribe to this conversation on GitHub.
Already have an account?
Sign in.
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
A pre-reservation 4xx from the platform reached the harness as the fixed string
"inference request denied"— even though the platform's own explanation was sitting inresponseBodyat that exact site. Combined with the platform saying only"unsupported inference parameter"without naming the parameter, a miner whose request carried one unrecognised field had no channel at all to learn its name.That is not hypothetical.
Cooking(rank 15 on v1) burned three submissions on a single unrecognisedreasoningkey — v3 shipped byte-identical to v2 on that line — because nothing anywhere in the stack ever said the word "reasoning".This is the last hop. ditto-assistant/ditto-platform#554 makes the platform name the key; this PR makes the name survive to the harness's stderr.
Change
platformRejectionMessage(body []byte) stringreadsmessageout of the platform's error envelope ({error_code, message, request_id}, permiddleware/error_envelope.py) and is used at theagentRequestRejectionssite inforwardChatCompletion. It goes to both destinations that matter: the harness's stderr and the operator's log line, which previously reported only the status code.Hardening, because this string crosses a trust boundary into a log:
log.Printfand a harness's stderr; a newline would let a relayed message forge a log entry..... Long enough for a schema refusal naming several fields, short enough that nothing can pump bulk text into either destination.Classification is deliberately untouched
This is the fleet-skew constraint, and it is the reason this change is safe to ship against validators running 0.34.1 through 0.37.3 concurrently:
usageUnavailable++,agentRequestRejections++.agent_request_rejections. These are the harness's own bytes, so that remains the correct bucket. Nothing moves to a no-fault code — no newvalidator_infrastructurecode is introduced, so there is nothing for ditto-subnet to map toscoring_errorand no risk of charging miners on older validators.{"error": …}. Only the human-readable value differs, which is why this cannot break an existing harness.TestASchemaRejectionNamesTheFieldToTheHarnessasserts every one of these explicitly alongside the new behaviour.Tests
TestPlatformRejectionMessageExtractsTheExplanation— pulls the field name out of a real envelope, including the multi-field case.TestPlatformRejectionMessageFallsBackOnAnythingUnusable— 7 malformed/empty bodies plus an oversized one all yield the fallback.TestPlatformRejectionMessageCannotForgeLogLinesOrRunLong— newline injection is neutralised; a 2000-byte message is bounded and says it was truncated.TestASchemaRejectionNamesTheFieldToTheHarness— end to end on the exact body that brokeCooking: harness sees 400, the{"error": …}shape, the platform's own string, and the wordreasoning; counters and attribution unchanged.TestAnUnparseableRejectionKeepsTheOldWording— an older platform is unaffected.go build ./...,go vet ./..., and the fullgo test ./...suite pass.Related
reasoningand other harmless fields instead of refusing them). Independent of this PR: either alone is an improvement, both together give the miner the field name end to end.agentRequestRejectionsis fix/split relay decline classification #120's counter) without duplicating or conflicting with them.validator_lease_audit, the fleet-health dashboard, or the scores read path.🤖 Generated with Claude Code