fix(copilot): let a raw container declare a permanent failure - #7960
fix(copilot): let a raw container declare a permanent failure#7960mitja-kleider wants to merge 3 commits into
Conversation
The document a caller builds itself needs the same write, and only UploadError's own classification of it should stay UploadError's. Signed-off-by: Mitja Kleider <mitja.kleider@aleph-alpha.com>
There was a problem hiding this comment.
🟡 Changes recommended
The current byte-for-byte “re-encode to self” protobuf check can reject valid ErrorDocument encodings, causing container-declared permanent errors to be misclassified as plain-text retryable errors.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Pull request overview
This PR improves raw-container failure reporting in flytecopilot by allowing a container to write a serialized core.ErrorDocument to its local error file and have that document propagated to remote storage without being reclassified by the sidecar, preserving the container-chosen Kind and Origin.
Changes:
- Detect
core.ErrorDocumentcontent in the container error file duringRecursiveUploadand surface it as a newContainerError. - Add
UploadErrorDocumentso callers can upload an explicitly constructed error document (while keepingUploadErrorbehavior unchanged). - Update
Sidecarto forward container-declared error documents untouched; add end-to-end tests covering both proto and plain-text error file behavior.
File summaries
| File | Description |
|---|---|
| flytecopilot/data/upload.go | Detects and returns a ContainerError when the error file contains an ErrorDocument. |
| flytecopilot/data/upload_test.go | Adds unit tests for proto-document vs plain-text error file handling. |
| flytecopilot/cmd/sidecar.go | Forwards ContainerError documents unchanged; preserves prior classification for uploader failures. |
| flytecopilot/cmd/sidecar_test.go | Adds an end-to-end test verifying the container’s error document is what the output reader observes. |
| flytecopilot/cmd/root.go | Introduces UploadErrorDocument and makes UploadError delegate to it. |
Review details
- Files reviewed: 5/5 changed files
- Comments generated: 1
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
A container's only channel for reporting its own failure is the error file in its output directory, and Sidecar rewrote whatever it found there as a RECOVERABLE OutputUploadFailed with no origin. A container that knows its failure is deterministic could not say so, and a raw-container task that exits non-zero instead is a system failure, spending the cluster's system-failure budget rather than the task's retries. Forward the document when the container wrote one, leaving copilot's own upload failures classified as before. Signed-off-by: Mitja Kleider <mitja.kleider@aleph-alpha.com>
e08b755 to
b868853
Compare
Tracking issue
No upstream issue. Related to #7922, which reconnected the error document to the reader
and listed this as deliberately out of scope:
This is a follow-up adding the permanent USER error.
Why are the changes needed?
A raw container has two ways to report failure and neither can express a permanent user
error.
Exiting non-zero is classified as a system failure: the task's
retriesis neverconsulted and the node is retried up to
max-node-retries-system-failuresinstead.Writing the error file is the documented alternative, and
RecursiveUploadpicks it up —but
Sidecarthen discards what the container said about it, rewriting every uploaderfailure as
OutputUploadFailed/RECOVERABLE/Originunset. A container that knowsits failure is deterministic has no way to say so, and
retriescannot express it either:the whole point of
retries=0is defeated by a system-failure budget the task does notcontrol.
Today the only workaround for us is the container speaking flyte's blob protocol itself:
build an
ErrorDocument, construct an S3 PUT and writeerror.pbinto the outputprefix.
What changes were proposed in this pull request?
Let the container write a serialized
core.ErrorDocumentto its error file and forward ituntouched, so the
KindandOriginit chose are what flyte acts on.flytecopilot/data/upload.go— when the error file's bytes are anErrorDocument,RecursiveUploadreturns the newContainerErrorcarrying it. A document isrecognised by structure, a parse that yields a code or a message, rather than by
re-encoding to the same bytes: the wire format is not canonical, so field order,
varint width and a field a newer idl added all differ legitimately between encoders.
proto.Unmarshaldoes accept some plain text by chance, but such a parse leaves theerror's own fields empty, and a document with neither a code nor a message says
nothing the plain-text path cannot.
flytecopilot/cmd/root.go—UploadErrorDocumentwrites a caller-built document;UploadErrorkeeps its signature and its recoverable classification, delegating.flytecopilot/cmd/sidecar.go—Sidecarforwards aContainerError's document andclassifies everything else exactly as before.
Backwards compatible by construction: an error file that is not a document still becomes
User Error: <contents>underOutputUploadFailed/RECOVERABLE.Two adjacent things left alone deliberately:
wrote its error and exited is done deciding), and its
Originis arguablyUSERratherthan unset. Both change existing behaviour for anyone relying on the retry, so they are
not in here.
_ERRORprotocol is not documented for raw-container authors as far as I can find.If there is a page for it, I'll document the document form there.
How was this patch tested?
New tests, each watched fail before the fix and pass after:
TestUploader_RecursiveUpload_ContainerErrorDocument— a document in the error filecomes back as
ContainerErrorwith the document intact. Before the fix it fails showingexactly today's behaviour, the serialized proto stringified into a message:
in chain: "User Error: \n2\n\x0fBenchmarkFailed\x12\x1dthe model rejected the prompt \x01".TestUploader_RecursiveUpload_ContainerErrorDocumentFromAnotherEncoder— the samedocument hand-encoded with the fields out of order and carrying a field this binary
does not know is still read as a document, with the container's code, message and
Originintact.TestUploader_RecursiveUpload_ContainerErrorMessage— a plain-text error file stillproduces
User Error: failedand noContainerError. Guards the compatibility claim;passes before and after.
TestSidecarForwardsTheContainersErrorDocument— end to end across the two components:Sidecarruns against a memory store, thenNewRemoteFileOutputReaderover the sameprefix must report
IsError(),IsRecoverable == false,Kind == USERand thecontainer's own code and message. Before the fix:
expected BenchmarkFailed, actual OutputUploadFailed.All pass,
gofmtandgo vetclean.Labels
fixed
Check all the applicable boxes
raw-container docs)