Skip to content

Leave a running session alone when restart's prompt gets no answer - #216

Merged
myobie merged 2 commits into
mainfrom
agent/restart-unanswered-prompt-declines
Nov 4, 2026
Merged

myobie merged 2 commits into
mainfrom
agent/restart-unanswered-prompt-declines

Conversation

@myobie

@myobie myobie commented Nov 4, 2026 •

Copy link
Copy Markdown
Contributor

pty restart no longer stops a running session when nobody answers its prompt. This applies #215's rule (decision 0020) to the other [Y/n] prompt, as Factory decided.

Before. restart asked Session "<name>" is running. Kill and restart? [Y/n] through the old ask, which has no terminal check. EOF read as an empty answer, which proceeds. A caller with no terminal stopped live work and started it again, although nobody agreed. Node leaves the session running at EOF.

Now. The prompt uses ask::ask_consent:

  • At a terminal, any line but a lone n restarts, an empty line included. This is unchanged.
  • Off a terminal, only y or yes restarts.
  • EOF never restarts.
  • With no answer, restart ends the prompt line and exits 1. The session is left running, and stderr shows:
    pty restart: Session "<name>" was not restarted: the restart prompt got no answer.
      Answer y, or pass -y to restart it without asking.
    
  • These are unchanged: a lone n exits 0, and -y restarts without asking.
  • -y already existed, so no new flag is added.

The old ask has no callers left, so it's removed. Both prompts now share one rule.

The publication check comes before the prompt. A revoked or unverifiable managed launch is now refused before restart asks anything. After a yes, it checks again before it stops the session, because an answer can take any time. Before, the check came after the prompt. That meant asking "Kill and restart?" and then refusing, and with no terminal the refusal couldn't be reached at all.

publication_fence::restart_refuses_revoked_binding_before_stopping_survivor runs restart survivor --force with no terminal and no -y. It failed on this PR's first head, d437d5bb, with the no-answer message. With this order it passes unchanged.

Every restart without -y in this repo:

  • crates/pty/tests/publication_fence.rs:163, the test above.
  • crates/pty-conformance/tests/restart_prompt.rs:59/73/86, this PR's own prompt tests.
  • The help example pty restart myserver (crates/pty/tests/fixtures/help/restart.txt). These are Node's exact bytes, and the example is still right at a terminal.
  • Every other test and doc passes -y, or only mentions restart in prose. That covers the conformance files and the README, docs/ and scripts/.
  • No script or completion runs it.

The same shape in attach, left as it is here: attach's dead-session restart also checks publication only after its prompt, and again under the locks. So a revoked dead session is asked Restart? [Y/n] and then refused. -r still reaches the refusal. I can move that check ahead of the prompt as its own change.

Node, measured against the pinned reference (f005432a) on Node 22.23.2, with no terminal. Rust before is main 9e46e7e8; Rust now is this branch:

stdin Node Rust before Rust now
</dev/null exit 0, left running stopped and restarted exit 1, left running
an empty line stopped and restarted stopped and restarted exit 1, left running
n exit 0, left running exit 0, left running exit 0, left running
y stopped and restarted stopped and restarted stopped and restarted
-y stopped and restarted stopped and restarted stopped and restarted

Decision and docs.

Release note. pty restart no longer stops a running session when its restart prompt gets no answer. That covers stdin at EOF, and stdin that isn't a terminal unless it gives y or yes. It exits 1 instead and leaves the session running. Any script or seat that relied on restart without a terminal must pass -y now.

I found no caller of pty restart as a subprocess in fractal at b923a6fb.

Tests. New file crates/pty-conformance/tests/restart_prompt.rs, with no terminal:

  • a_running_session_is_left_running_at_eof: </dev/null.
  • a_running_session_is_left_running_without_a_yes: a piped empty line, then relayed input.
  • Each decline checks exit 1 and the message. It also checks that the same daemon pid is alive, that the session is listed as running, and that its command didn't start again.
  • a_piped_yes_restarts_a_running_session: the old daemon exited and the command started again.
  • yes_flag_restarts_without_a_terminal.
  • The two restart cases set PTY_SESSION so that restart skips its trailing attach, as the existing restart -y conformance tests do.
  • divergences.toml records the two decline tests as intended, side node.

Also here. attach_no_restart.rs has one line from #215 reformatted to rustfmt's layout. I had missed that diff when checking #215. Formatting is report-only in CI, so nothing was red. The file's other rustfmt diffs are older code and are left as they are.

Measured on Linux at this head, based on main 9e46e7e8:

  • scripts/conformance-both.sh restart_prompt attach_no_restart against the pinned Node build. Exactly the five ledger entries differ: two new, three from Decline attach's restart prompt when nobody answers (#214) #215. restart_prompt alone gave Node 2/2 and Rust 4/0.
  • Control: main's binary on restart_prompt fails the two decline tests and passes the other two.
  • cargo test -p pty-conformance against this head's binary: 697 passed, 0 failed.
  • cargo test -p pty --no-fail-fast: all 47 test files ran. 476 passed and 1 failed: interactive::typing_filters_the_list_and_escape_clears_it, which fails in my checkout (the fuzzy filter matches the checkout path) and passes in CI. publication_fence passes.
  • A correction. The "363 passed" I quoted on Decline attach's restart prompt when nobody answers (#214) #215 and on this PR's first head came from runs without --no-fail-fast. Cargo stopped at that same failing binary, so seven later test files never ran there, publication_fence.rs among them. Decline attach's restart prompt when nobody answers (#214) #215's CI ran all of them and was green.
  • The table above was measured with no terminal on Node, on main and on this branch.

pty restart asks Kill and restart? [Y/n] before it stops a running
session, through the same ask that #215 replaced for attach. At EOF that
read as yes, so a caller with no terminal stopped live work and started
it again although nobody agreed to it. Node leaves the session running
there and exits 0.

The prompt now uses ask_consent, by decision 0020's rule:

- At a terminal, any line but a lone n restarts, as before.
- Off a terminal, only y or yes restarts.
- EOF never does.

With no answer, restart exits 1, leaves the session running and names
-y. A lone n still exits 0 and -y still restarts without asking. The old
ask has no callers left and is removed.

Decision 0020 gains an addendum and parity.md a row. The conformance
ledger records the two new tests that fail on Node as intended. One of
#215's test lines is reformatted to rustfmt's layout.
restart checked the publication binding only after its prompt. It asked
Kill and restart? and then refused a revoked or unverifiable managed
launch, and with no terminal the refusal could not be reached at all:
publication_fence's restart_refuses_revoked_binding_before_stopping_survivor
runs restart --force without -y and got the no-answer message instead.

The check now runs before the prompt. After a yes it runs again before
the session is stopped, since an answer can take any time. Decision 0020's
addendum says so.
@myobie

myobie commented Nov 4, 2026

Copy link
Copy Markdown
Contributor Author

Independent read GO at exact head e2e4964c88dd7744288df74ec2c2630db8faee3c. All 9 checks are SUCCESS on this head and the PR is CLEAN. I read the first head (d437d5bb) in full and the follow-up commit as a delta.

Read:

  • pty restart on a running session asks Kill and restart? [Y/n]. Before this change, stdin at EOF or not a terminal counted as "yes", so a non-interactive caller stopped live work without consenting. The prompt now counts only an answer someone gave: at a terminal any line but a lone n restarts (as before; Ctrl+D now exits 1); off a terminal only y or yes restarts, and EOF never does.
  • With no answer, restart ends the prompt line, prints that the session was not restarted and that -y restarts it without asking, exits 1, and leaves the session running. A lone n still exits 0, and -y is unchanged. The old prompt helper had no callers left and is removed.
  • This is the same rule as attach's dead-session prompt, so it extends decision 0020 with an addendum, a parity row, two intended divergence entries and a README sentence. Pinned Node, run with no terminal, leaves the session running at EOF and exits 0, and restarts on any line but n, so this departs from it only in the exit code at EOF and in needing y off a terminal.
  • The first head failed one existing test: it runs restart --force on a managed session with no terminal and expects a revoked-binding refusal, but now hit the prompt first. The follow-up moves the publication check before the prompt, so a revoked or unverifiable launch is refused without asking, and checks again after a yes before stopping the session, since an answer can take any time. The existing test passes unchanged. -y and non-running sessions behave as before.
  • Tests: a new conformance file checks that EOF, an empty line and relayed input leave the same daemon running and the command not restarted, that a piped y restarts, and that -y restarts with no terminal. No wire-format change and no tier-1 file (events.jsonl, <id>.json, docs/disk-layout.md).
  • A correction from the author: their earlier local test counts missed some test files, because cargo stopped at a test that fails only in their checkout path. With --no-fail-fast all pty test files run (476 passed, 1 checkout-path failure), and CI ran everything.

Release note (behavior change): pty attach and pty restart no longer restart a session when their restart prompt gets no answer. That covers stdin at EOF, and stdin that is not a terminal unless it gives y or yes. They exit 1 instead: attach without running the stored command, restart without stopping the running session. Any script that relied on either restarting non-interactively must pass -r to attach or -y to restart now. A search of the repository and the fleet's own sources found no such caller.

Risks (low):

  • At a terminal, typing the word no still restarts, because only a lone n declines (Node parity). A possible follow-up.
  • attach's dead-session prompt still asks before the publication refusal for a revoked dead session; -r reaches the refusal. A possible small follow-up.

@myobie
myobie merged commit 79b96c3 into main Nov 4, 2026
9 checks passed
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