The mechanism
A package script whose entire body is a single gjsify … command exits 0 no matter what that command did.
Measured on the pinned CLI 0.16.3 — runScript in @gjsify/cli/dist/cli.gjs.mjs:
let Y = isGjs() ? gjsifyInProcessArgv(I, a) : null;
if (Y) {
try { const { runCli } = await import(...); await runCli(Y); }
catch (e) { logger.error(e.message); process.exit(1); }
process.exit(process.exitCode == null ? 0 : Number(process.exitCode));
}
gjsifyInProcessArgv returns non-null only when the body tokenizes as one plain gjsify … command (tokenizeSimpleCommand bails on any of |&;<>`$()\ * ? { } [ ] ~ # !). On that path the sub-command runs in-process and the wrapper exits with process.exitCode ?? 0 — and gjsify build and gjsify run report failure without setting process.exitCode. Anything with a shell operator takes the spawn path and the status survives. gjsify tsc is unaffected because it spawns gjs.
Reproduction:
"p1": "gjsify run /tmp/throws.gen.js" -> gjsify workspace <pkg> p1 == exit 0
"p2": "gjsify run /tmp/throws.gen.js && echo SECOND" -> exit 1, SECOND not printed
"p3": "gjs -m /tmp/throws.gen.js" -> exit 1
"p4": "exit 7" -> exit 1
What is still affected
#178 chained the leaves in learn, translations and examples, which is where it was actively hiding failures (the tutorial artifact check and the catalog check both exited 0 while reporting problems). These remain:
packages/app-gnome: build:gjs, build:resources, build:schema — so gjsify workspace @learn6502/app-gnome build reports success when the bundle, the gresource or the GSettings schema failed to build. This one ships.
packages/app-web: build:app (and therefore build).
packages/cli: build.
- root:
build (gjsify foreach --topological-dev build), check (gjsify foreach -t check), setup, start:*, format, lint — worth measuring whether foreach propagates before changing anything.
check scripts that are gjsify tsc … (core, common-ui, app-web, app-android, app-gnome's check:typescript) were measured and do propagate — verified by introducing a type error and getting exit 1.
Fix
Upstream, in gjsify: the in-process path should take the sub-command's status rather than reading a process.exitCode its commands never set. Until that lands, AGENTS.md (§ Package scripts) records the rule so the chains are not "simplified" back into single-command steps.
The mechanism
A package script whose entire body is a single
gjsify …command exits 0 no matter what that command did.Measured on the pinned CLI 0.16.3 —
runScriptin@gjsify/cli/dist/cli.gjs.mjs:gjsifyInProcessArgvreturns non-null only when the body tokenizes as one plaingjsify …command (tokenizeSimpleCommandbails on any of|&;<>`$()\ * ? { } [ ] ~ # !). On that path the sub-command runs in-process and the wrapper exits withprocess.exitCode ?? 0— andgjsify buildandgjsify runreport failure without settingprocess.exitCode. Anything with a shell operator takes the spawn path and the status survives.gjsify tscis unaffected because it spawnsgjs.Reproduction:
What is still affected
#178 chained the leaves in
learn,translationsandexamples, which is where it was actively hiding failures (the tutorial artifact check and the catalog check both exited 0 while reporting problems). These remain:packages/app-gnome:build:gjs,build:resources,build:schema— sogjsify workspace @learn6502/app-gnome buildreports success when the bundle, the gresource or the GSettings schema failed to build. This one ships.packages/app-web:build:app(and thereforebuild).packages/cli:build.build(gjsify foreach --topological-dev build),check(gjsify foreach -t check),setup,start:*,format,lint— worth measuring whetherforeachpropagates before changing anything.checkscripts that aregjsify tsc …(core,common-ui,app-web,app-android,app-gnome'scheck:typescript) were measured and do propagate — verified by introducing a type error and getting exit 1.Fix
Upstream, in gjsify: the in-process path should take the sub-command's status rather than reading a
process.exitCodeits commands never set. Until that lands,AGENTS.md(§ Package scripts) records the rule so the chains are not "simplified" back into single-command steps.