From e9e7617fa19f477a50e9f025b4d73ba4f0f8a873 Mon Sep 17 00:00:00 2001 From: debuggingfuture Date: Sat, 15 Aug 2026 21:27:57 +0800 Subject: [PATCH] fix(offload-test): a dead stage's marker keeps the platform's own incident id MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `ExecFailed: exec failed (exit -1): internal error; reference = aggq3f5m407e2vb2ht76l2vf` is what a dying container reports. The marker recorded the CLASS and dropped the reference — which is backwards: the class is the half a consumer can already infer from the fact that their stage died, and the reference is the only half that identifies the incident to the platform's operator. It matters now because the resource explanations are gone. On the consumer that motivated the staging work, the heaviest stage peaks at 2.2 GiB of 11.9 GiB with 8.4 GB of disk to spare, and dies at 137s, 647s and 1284s against successes at 2128s and 2176s. Nothing is scarce and no duration is safe, so the next question is one only the platform can answer — and answering it needs the id. EXTRACTED rather than interpolated. The marker is shell-quoted into a `printf '%s\n' ''`, so free vendor text could close the quote; a `[A-Za-z0-9_-]` reference and a signed integer exit code cannot. The test pins that charset rather than trusting the shape of today's message. --- runs/offload-test.test.ts | 30 ++++++++++++++++++++++++++++++ runs/offload-test.ts | 26 +++++++++++++++++++++++--- 2 files changed, 53 insertions(+), 3 deletions(-) diff --git a/runs/offload-test.test.ts b/runs/offload-test.test.ts index 2c67090..3e3b306 100644 --- a/runs/offload-test.test.ts +++ b/runs/offload-test.test.ts @@ -787,6 +787,36 @@ describe("offload-test staged mode", () => { }, ); + it.effect("a dead stage's marker keeps the platform's own incident id", () => { + const { layer, handles } = makeCFRuntimeTest({ + sandboxProgram: { + // What a dying container actually reports. The class is the half a + // consumer can already infer; the reference is the half only the + // platform's operator can act on, and it used to be dropped. + "run-a": { + fail: "ExecFailed", + exitCode: -1, + stderrTail: "exec failed (exit -1): internal error; reference = aggq3f5m407e2vb2ht76l2vf", + }, + }, + config: { + "offload-test.stages:owner/name": "a", + "offload-test.command:owner/name:a": "run-a", + }, + }); + + return Effect.gen(function* () { + yield* Effect.exit(offloadTest.run(webhookInput)); + const markerWrite = handles.sandbox.execs.find((e) => e.command.includes("stage=a")); + expect(markerWrite?.command).toContain("reference=aggq3f5m407e2vb2ht76l2vf"); + // Shell-safe by construction: whatever lands after `reference=` is drawn + // from `[A-Za-z0-9_-]` only, so no vendor text can close the quote the + // marker is printed inside. + const id = /reference=([^\s']*)/.exec(markerWrite?.command ?? "")?.[1]; + expect(id).toMatch(/^[A-Za-z0-9_-]+$/); + }).pipe(Effect.provide(layer)); + }); + it.effect( "an Action dispatch that passes `command` stays single-exec even with stages set", () => { diff --git a/runs/offload-test.ts b/runs/offload-test.ts index 943f6e1..4b65956 100644 --- a/runs/offload-test.ts +++ b/runs/offload-test.ts @@ -805,9 +805,29 @@ export const offloadTest = defineRun({ }); const elapsedMs = (yield* io.now) - stageStartMs; const elapsedS = Math.round(elapsedMs / 1000); - // Label (STAGE_LABEL_RE), tag, and number only — shell-quote-safe - // by construction. - const markerLine = `stage=${stage.label} error=${errorClass} elapsedMs=${elapsedMs}`; + // Label (STAGE_LABEL_RE), tag, numbers, and — when the platform + // supplied one — its own incident id. + // + // `ExecFailed: exec failed (exit -1): internal error; reference = + // aggq3f5m407e2vb2ht76l2vf` is what a dying container reports, and + // that reference is the only thing that identifies the incident to + // the platform's operator. The marker used to record the CLASS and + // drop the id, which is the half a consumer can already infer from + // the fact that their stage died. + // + // EXTRACTED, not interpolated: the marker is shell-quoted into a + // `printf '%s\n' ''`, so free vendor text could close the + // quote. A `[A-Za-z0-9_-]` id and a signed integer cannot. + const rendered = Option.match(Cause.failureOption(exit.cause), { + onSome: (f) => `${String((f as { cause?: unknown }).cause ?? "")} ${String((f as { message?: unknown }).message ?? "")}`, + onNone: () => "", + }); + const reference = /reference\s*=\s*([A-Za-z0-9_-]{1,64})/.exec(rendered)?.[1]; + const platformExit = /exit\s+(-?\d{1,5})\)/.exec(rendered)?.[1]; + const markerLine = + `stage=${stage.label} error=${errorClass} elapsedMs=${elapsedMs}` + + (platformExit !== undefined ? ` exit=${platformExit}` : "") + + (reference !== undefined ? ` reference=${reference}` : ""); // The marker rides the marker exec's OWN log stream: `sandbox.exec` // streams stdout to an R2 log key and returns it as `logPath`, so // uploading THAT key in R2-source mode (no `container` — same mode