mirror of
https://github.com/l0ng-ai/tty7.git
synced 2026-09-22 00:02:23 +00:00
fix(cli): a server that closes the event stream is not a success
`tty7 events` blocks forever by contract, so returning at all means the control connection went away with the server — and it returned 0. A reader whose server stopped mid-run was told nothing had happened; the loop consuming its lines simply stopped receiving any, with no exit code to branch on. Verified against an isolated daemon: stopping the server ended the stream and the CLI exited 0. It exits 1 now and says so on stderr. Nothing extra goes to stdout, so a reader parsing NDJSON is never handed a line of a shape it has not seen — the same split the rest of the verbs use. An interrupted run is unaffected: a signal takes the process rather than this path.
This commit is contained in:
@@ -1157,7 +1157,25 @@ fn events(json_mode: bool, backend: &mut dyn Backend) -> Result<Outcome> {
|
||||
}
|
||||
Ok(())
|
||||
})?;
|
||||
report("", Value::Null)
|
||||
// This verb blocks forever by contract, so returning at all means the
|
||||
// stream ended under it — and the only thing that ends it is the control
|
||||
// connection going away with the server. Exiting 0 there told a reader
|
||||
// that watched a server stop mid-run that nothing had happened, and the
|
||||
// loop consuming the lines simply stopped receiving any. An interrupted
|
||||
// run does not come through here: a signal takes the process, not this
|
||||
// path.
|
||||
//
|
||||
// Nothing extra is written to stdout, so a reader parsing NDJSON is not
|
||||
// handed a line of a shape it has never seen; the news goes to stderr and
|
||||
// the exit code, which is the rule everywhere else in this CLI.
|
||||
eprintln!("tty7: the server closed the event stream");
|
||||
Ok(Outcome::Exit(
|
||||
1,
|
||||
Report {
|
||||
human: String::new(),
|
||||
json: Value::Null,
|
||||
},
|
||||
))
|
||||
}
|
||||
|
||||
fn event_line(event: &ControlEvent) -> String {
|
||||
@@ -4365,6 +4383,20 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_stream_that_ends_under_events_is_not_a_success() {
|
||||
// `events` blocks forever by contract, so a return means the control
|
||||
// connection went away with the server. Exiting 0 told a reader that
|
||||
// watched a server stop mid-run that nothing had happened.
|
||||
let mut backend = mock();
|
||||
backend.events.push(ControlEvent::LayoutResync);
|
||||
let out = run_cli(&["tty7", "events"], &Context::default(), &mut backend);
|
||||
match out {
|
||||
Outcome::Exit(1, _) => {}
|
||||
other => panic!("a closed stream should exit nonzero, got {other:?}"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn an_event_line_is_a_sentence_and_never_a_debug_dump() {
|
||||
use tty7_core::core::machine::{PaneNode, PaneRecord, Tab};
|
||||
|
||||
@@ -251,6 +251,10 @@ Without `--json` each line is a sentence meant to be read, and its wording is
|
||||
not a format to parse — `--json` below is. The kinds and payloads are stable;
|
||||
the prose is not.
|
||||
|
||||
Because it blocks forever, returning at all means the server closed the stream:
|
||||
that exits 1 and says so on stderr, so a loop consuming the lines does not read
|
||||
the end of its input as the end of the work.
|
||||
|
||||
Each line is one externally tagged object, `{"<kind>": {…}}`:
|
||||
|
||||
| Kind | Payload |
|
||||
|
||||
Reference in New Issue
Block a user