Repository navigation
fix(modal): surface the reason that ended the worker - #335
Conversation
The worker's top-level catch discarded the rejection value, so every failure outside the two classes `diagnosticCodeForError` recognises produced the literal string "worker terminated" and exit code 1. Binding the error alone would not have helped: `runChecked` drains child stderr into a no-op and throws `OperationalDispositionError` with no `cause`, so the reason was already gone two layers earlier. Retain a bounded, redacted stderr tail in `runChecked` and attach it as `cause`, then render the whole `cause` chain at the top-level catch. The chain is rendered by reading only `name`, `code` and `message` at each link — never by inspecting the error object, because an `execFileSync` ENOBUFS `SystemError` carries `output`/`stdout`/`stderr` holding up to the whole captured workspace diff. The mirror leak in `public-worker.ts` — 4000 raw stderr bytes embedded as a `cause`, hidden today only because `.stack` does not render it — now goes through the same sanitizer, which is `public-worker.ts`'s own redaction path lifted into a shared module. Refs #307. Supersedes #309. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…arry `describeWorkerTermination` reads only `name`, `code` and `message`, so the class of failure #307 is actually chasing -- an unanticipated error in the worker's own code -- prints "TypeError: Cannot read properties of undefined" and names no file and no line. The stated reason for dropping the stack does not hold: `Error.prototype.stack` is a plain string of `name: message` plus frames and never carries an error's own enumerable properties, so an `execFileSync` ENOBUFS `SystemError`'s `output`/`stdout`/`stderr` cannot reach it. That is now measured by a test rather than assumed. Add `workerTerminationStack`, which redacts and bounds those frames the same way every other diagnostic string in this module is bounded, and print it from the top-level catch. Also make a rejection with `undefined` or `null` name itself instead of collapsing to the bare prefix the issue is about. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
| await appendGenericLog("operation-failed"); | ||
| throw new OperationalDispositionError(failureCategory(options)); | ||
| throw new OperationalDispositionError(failureCategory(options), { | ||
| cause: childExitFailureCause(options.label, exitCode, stderrTail) |
There was a problem hiding this comment.
Injected credential lacks exact redaction
If the scoring subprocess writes an opaque ULTRAFUZZ_EVAL_JUDGE_API_KEY value to stderr, childExitFailureCause sanitizes it without the injected credential in forbiddenSecretValues, so pattern-based redaction can leave the credential visible in the terminal diagnostic.
How this was verified: The judge credential flows through options.env into the child, while the resulting stderr cause is constructed without passing that value to the exact-value redactor.
Knowledge Base Used: Modal remote benchmark execution
Prompt To Fix With AI
This is a comment left during a code review.
Path: packages/modal/src/worker.ts
Line: 533
Comment:
**Injected credential lacks exact redaction**
If the scoring subprocess writes an opaque `ULTRAFUZZ_EVAL_JUDGE_API_KEY` value to stderr, `childExitFailureCause` sanitizes it without the injected credential in `forbiddenSecretValues`, so pattern-based redaction can leave the credential visible in the terminal diagnostic.
**How this was verified:** The judge credential flows through `options.env` into the child, while the resulting stderr cause is constructed without passing that value to the exact-value redactor.
**Knowledge Base Used:** [Modal remote benchmark execution](https://app.greptile.com/monad-foudnation/-/custom-context/knowledge-base/monad-developers/ultrafuzz/-/docs/modal.md)
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.| return (options.keep === "head" ? bytes.subarray(0, maxBytes) : bytes.subarray(bytes.length - maxBytes)).toString( | ||
| "utf8" | ||
| ); |
There was a problem hiding this comment.
Truncation splits UTF-8 characters
Slicing the encoded buffer at an arbitrary byte offset can split a multibyte character, causing long termination reasons or stack diagnostics to contain replacement characters and lose text at the boundary.
Knowledge Base Used: Modal remote benchmark execution
Prompt To Fix With AI
This is a comment left during a code review.
Path: packages/modal/src/worker-diagnostics.ts
Line: 47-49
Comment:
**Truncation splits UTF-8 characters**
Slicing the encoded buffer at an arbitrary byte offset can split a multibyte character, causing long termination reasons or stack diagnostics to contain replacement characters and lose text at the boundary.
**Knowledge Base Used:** [Modal remote benchmark execution](https://app.greptile.com/monad-foudnation/-/custom-context/knowledge-base/monad-developers/ultrafuzz/-/docs/modal.md)
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.Four files conflicted; every resolution keeps both sides' behaviour. workflow.tsx -- both destructuring imports were unions, not choices. Kept #344's `checkInvariantSourcePinned` / `invariantPinnedSourceRefExists` and #342's `validateWorkspacePatchCapture` alongside #188's cloud-selected-task, finding-provenance and dynamic-topology symbols. The rest of the file merged without overlap: #334's `invariantSuiteGitPaths` still bounds all five invariant-discovery enumerations, #342's single ancestor-wide suite budget and depth bound are intact, #343's per-path publisher resolution still fails closed on unordered siblings, and #188's dynamic `taskSpecs` reassignment happens before any of those helpers read it, so they see the expanded node set. public-worker.ts -- kept #188's `REFERENCE_GITHUB_TOKEN_ENV` import and dropped `redactSecretsInText`, whose only consumer (`sanitizePublicDiagnosticMessage`) #335 deleted when it routed the stderr tail through `sanitizeWorkerDiagnosticMessage`. The two compose: the reference token #188 adds to `publicBenchmarkWorkerSecretValues` is now redacted from the bounded stderr tail as well. node-provider.test.ts -- unioned `logicalDispatchFingerprint` and `verificationMarker` into one options type, and moved #339's three new verification-marker tests onto #188's two-argument `createResultArchive` signature so their bundles carry a real `logical_dispatch_fingerprint` instead of bypassing the provenance check. topology-transform.test.ts -- kept both new tests. #342's invariant-only exclusion test now reads the `REPOSITORY_ROOT` constant #188 hoisted rather than recomputing the path, so the file keeps one spelling of the repository root. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Closes #307.
PR #309 tried the one-line fix and was correctly closed: the reason is destroyed upstream, so printing at
:619alone emits onlyOperationalDispositionError: worker operation failed.This retains a bounded, sanitized stderr tail in
runCheckedand attaches it ascausebefore touching the print — reversing that order dumps up to 32 MB of workspace diff. Also routespublic-worker.ts:928-933(which embedded 4000 raw stderr bytes) through the same redactor, and adds a frame-only stack plus aString(error)fallback so a null rejection no longer prints a bareworker terminated:.Known limitation: the
worker.tshalf cannot be unit-tested — it reads/data,/opt/ultrafuzzandrequiredEnvat module scope. Thepublic-worker.tshalf is covered.Greptile Summary
This PR retains and sanitizes bounded child stderr, attaches it to worker failure causes, and emits bounded cause-chain and stack diagnostics when a Modal worker terminates.
Confidence Score: 4/5
The PR appears safe to merge with non-blocking follow-up recommended for exact credential redaction and Unicode-safe diagnostic truncation.
The failure-reporting flow is bounded and generally sanitized, but opaque judge credentials are not supplied to the exact-value redactor and long Unicode diagnostics can be malformed at byte boundaries.
Files Needing Attention: packages/modal/src/worker.ts, packages/modal/src/worker-diagnostics.ts
Security Review
The new private-worker stderr path does not provide the injected judge credential as an exact forbidden value, so an opaque credential echoed by a failed scoring child can bypass pattern-based redaction and reach terminal stderr.
Important Files Changed
Sequence Diagram
Prompt To Fix All With AI
Reviews (1): Last reviewed commit: "fix(modal): keep the failing frames the ..." | Re-trigger Greptile
Context used: