Follow-up to #1190 / #1180 (fixed in #1195). The fix for apify run swallowing non-zero exit codes was applied locally in RunCommand (src/commands/run.ts):
} catch (err) {
process.exitCode = (err as ExecaError).exitCode ?? 1;
}
This is correct and contained, but exit-code handling for execWithLog children is inconsistent across the codebase, and the localized fix points at a deeper root cause:
exec.ts discards its own work. spawnPromised builds a friendly new Error(message) (with the exit code available on the cause), prints it via error(), then re-throws err.cause (the raw ExecaError) — so the friendly message is thrown away on the propagation path, and any consumer that lets the error bubble to the command framework gets a double-printed, noisy execa message.
- Three different failure strategies exist for the same event:
run.ts sets process.exitCode; create.ts captures git init failures into a bespoke result struct; create.ts/upgrade.ts dependency installs let the error bubble to the framework (which collapses to exit 1, losing the child's real code).
- The framework catch (
apify-command.ts) only does process.exitCode ||= 1, so the exact child code is lost unless each command hand-rolls propagation.
Proposed direction
Make execWithLog/exec.ts throw a single clean, already-logged error carrying the child's exitCode (e.g. an alreadyLogged marker), and have the framework set process.exitCode ||= err.exitCode ?? 1 while skipping the duplicate error() when already logged. Then individual commands drop their bespoke catches (RunCommand keeps only its finally for input cleanup) and exit-code propagation works uniformly for every execWithLog consumer.
Acceptance
Any command spawning a child via execWithLog surfaces the child's exact exit code, with no double-printed error message, and without per-command catch blocks.
Follow-up to #1190 / #1180 (fixed in #1195). The fix for
apify runswallowing non-zero exit codes was applied locally inRunCommand(src/commands/run.ts):This is correct and contained, but exit-code handling for
execWithLogchildren is inconsistent across the codebase, and the localized fix points at a deeper root cause:exec.tsdiscards its own work.spawnPromisedbuilds a friendlynew Error(message)(with the exit code available on the cause), prints it viaerror(), then re-throwserr.cause(the rawExecaError) — so the friendly message is thrown away on the propagation path, and any consumer that lets the error bubble to the command framework gets a double-printed, noisy execa message.run.tssetsprocess.exitCode;create.tscapturesgit initfailures into a bespoke result struct;create.ts/upgrade.tsdependency installs let the error bubble to the framework (which collapses to exit1, losing the child's real code).apify-command.ts) only doesprocess.exitCode ||= 1, so the exact child code is lost unless each command hand-rolls propagation.Proposed direction
Make
execWithLog/exec.tsthrow a single clean, already-logged error carrying the child'sexitCode(e.g. analreadyLoggedmarker), and have the framework setprocess.exitCode ||= err.exitCode ?? 1while skipping the duplicateerror()when already logged. Then individual commands drop their bespoke catches (RunCommandkeeps only itsfinallyfor input cleanup) and exit-code propagation works uniformly for everyexecWithLogconsumer.Acceptance
Any command spawning a child via
execWithLogsurfaces the child's exact exit code, with no double-printed error message, and without per-command catch blocks.