Skip to content

fix: a headless pass that never opened its source exits non-zero - #13

Merged
mysma-9403 merged 1 commit into
mainfrom
fix/headless-exit-code
Oct 11, 2026
Merged

mysma-9403 merged 1 commit into
mainfrom
fix/headless-exit-code

Conversation

@mysma-9403

Copy link
Copy Markdown
Contributor

xtce-gs run --headless --record pass.dat under a supervisor exited 0 after a second
having recorded nothing. A cron wrapper or a set -e script carried on as though the pass had
happened, and Restart=on-failure never fired.

Three endings collapsed onto one exit code: a replay that reached the end of its file (a
success), a source that could not be opened, and a source that died mid-pass and did not come
back.

What makes it a bug rather than a policy is that the split was arbitrary from the operator's
side. A missing definition already exited 1, because it is opened on the caller's thread. A
missing recording exited 0, because it is opened one spawn later inside the acquisition
task. Two spellings of the same mistake — a file I named on the command line is not there —
and nothing a user could predict separates them.

Session::is_running answers whether acquisition stopped. Session::outcome now answers
which ending: Ended, Stopped, SourceFailed, DecoderGone, recorded at each of the
task's exits. First writer wins, because a source that dies is also a queue that then drains,
and the first ending is the one that explains the pass. Two of them are failures, and
headless reports those as CliError::Pass.

Measured before and after:

before after
replay --headless mission.xml no-such-file.dat 0 1
run --headless --source tcp://127.0.0.1:1 … 0 1
a replay that decodes all 7 200 packets 0 0

Found by a review sweep looking for the class of defect that 536 passing tests missed twice
today; confirmed by running it. 542 tests, fmt and clippy clean.

`xtce-gs run --headless --record pass.dat` under a supervisor exited 0 after a
second having recorded nothing, and a cron wrapper or a `set -e` script carried
on as though the pass had happened. Three endings collapsed onto one exit code:
a replay that reached the end of its file (which is a success), a source that
could not be opened, and a source that died mid-pass and did not come back.

The split was arbitrary from the operator's side, which is what makes it a bug
rather than a policy: a missing *definition* already exited 1, because it is
opened on the caller's thread, while a missing *recording* exited 0, because it
is opened one spawn later inside the acquisition task. Two spellings of the
same mistake, two different exit codes.

`Session::is_running` answers whether acquisition stopped. `Session::outcome`
now answers which ending — Ended, Stopped, SourceFailed, DecoderGone — recorded
at each of the task's exits, first writer winning because a source that dies is
also a queue that then drains. Two of those are failures and `headless` reports
them as `CliError::Pass`.

Measured, before and after:

    replay --headless mission.xml no-such-file.dat   0 -> 1
    run --headless --source tcp://127.0.0.1:1 …      0 -> 1
    a replay that decodes all 7200 packets           0 -> 0

542 tests, fmt and clippy clean.
@mysma-9403
mysma-9403 merged commit 0a0a56b into main Oct 11, 2026
1 check passed
@mysma-9403
mysma-9403 deleted the fix/headless-exit-code branch October 11, 2026 20:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant