Summary
A command that exits nonzero is not remembered. If its declared outputs exist on disk when the next build starts, that build reports "Nothing to do (up to date)" and exits 0 — so the failure disappears and the tree is left holding whatever the failed run produced, or whatever a previous successful run left behind.
Reproduced
mkdir p && cd p && touch Tupfile.ini
echo ORIGINAL > src.txt
printf ': src.txt |> cp %%f %%o |> out.txt\n' > Tupfile
putup configure -B . && putup -B . # out.txt = ORIGINAL
echo "NEW CONTENT" > src.txt
printf ': src.txt |> cp %%f %%o && false |> out.txt\n' > Tupfile # command now fails
build 2: [.] Build completed: 0 commands (1 failed) exit 1 out.txt = NEW CONTENT
build 3: [.] Nothing to do (up to date). exit 0 out.txt = NEW CONTENT
Build 3 reports success for a command whose only execution exited 1.
Why it matters
putup -k in CI, followed by any later putup, reports green. I hit this while iterating on a compile error: a build with failures was followed by "Nothing to do", and the link had silently reused stale .o files from the previous successful build. It took a deliberate probe to notice, because nothing in the second build's output suggests anything is wrong.
The control cases both behave correctly, which localizes it:
- A failing command that produces no output keeps failing on every subsequent run (
0 commands (1 failed), exit 1) — retry scheduling itself is fine.
- Without
-k, a single failing command also keeps failing, because the build aborts before recording anything.
So the trigger is: the run completed (with -k, or with the failure late in the schedule) and the failed command's outputs exist.
Suspected mechanism
Not verified — the observed behaviour is consistent with the index recording the command's input state after a run in which that command failed, so the next build's change detection sees nothing to do. Exit status does not appear to be part of the recorded state.
Suggested direction
A command's recorded state should not advance unless it succeeded. Either skip index update for failed commands, or persist a failed marker that forces a re-run regardless of output presence.
Provenance
Found incidentally while iterating on #185's compile errors. Reproduced from scratch with the recipe above before filing.
🤖 Generated with Claude Code
https://claude.ai/code/session_01TLob4Ef3qyMd3Dnp9q3XmA
Summary
A command that exits nonzero is not remembered. If its declared outputs exist on disk when the next build starts, that build reports "Nothing to do (up to date)" and exits 0 — so the failure disappears and the tree is left holding whatever the failed run produced, or whatever a previous successful run left behind.
Reproduced
Build 3 reports success for a command whose only execution exited 1.
Why it matters
putup -kin CI, followed by any laterputup, reports green. I hit this while iterating on a compile error: a build with failures was followed by "Nothing to do", and the link had silently reused stale.ofiles from the previous successful build. It took a deliberate probe to notice, because nothing in the second build's output suggests anything is wrong.The control cases both behave correctly, which localizes it:
0 commands (1 failed), exit 1) — retry scheduling itself is fine.-k, a single failing command also keeps failing, because the build aborts before recording anything.So the trigger is: the run completed (with
-k, or with the failure late in the schedule) and the failed command's outputs exist.Suspected mechanism
Not verified — the observed behaviour is consistent with the index recording the command's input state after a run in which that command failed, so the next build's change detection sees nothing to do. Exit status does not appear to be part of the recorded state.
Suggested direction
A command's recorded state should not advance unless it succeeded. Either skip index update for failed commands, or persist a failed marker that forces a re-run regardless of output presence.
Provenance
Found incidentally while iterating on #185's compile errors. Reproduced from scratch with the recipe above before filing.
🤖 Generated with Claude Code
https://claude.ai/code/session_01TLob4Ef3qyMd3Dnp9q3XmA