Skip to content

Decline attach's restart prompt when nobody answers (#214) - #215

Merged
myobie merged 1 commit into
mainfrom
agent/attach-unanswered-prompt-declines
Nov 4, 2026
Merged

myobie merged 1 commit into
mainfrom
agent/attach-unanswered-prompt-declines

Conversation

@myobie

@myobie myobie commented Nov 4, 2026

Copy link
Copy Markdown
Contributor

Fixes #214. A dead session now restarts from attach's prompt only when someone answers it. This change departs from Node on purpose, and decision 0020 records it.

Before. attach on a dead session asked Restart? [Y/n] and read stdin with no terminal check. Any line but a lone n restarted, and so did EOF, which read as an empty answer. A caller with no terminal reran the stored command although nobody agreed to it. --force only allows nesting, so it changed nothing here.

Now. The prompt counts only an answer someone gave (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, in any case, restarts.
  • EOF never restarts, at a terminal or not.
  • With no answer, attach ends the prompt line and exits 1. It keeps the exited record and prints:
    pty attach: Session "<name>" was not restarted: the restart prompt got no answer.
      Answer y, or pass -r to restart it without asking.
    
  • These are unchanged: a lone n exits 0, -r restarts without asking, and --force answers nothing.

Node, measured. I built the pinned reference (f005432a) and ran each case without a terminal. Rust before is main ddfcf093:

stdin Node (22.23.2 and 24.18.0) Rust before Rust now
</dev/null exit 0, not restarted restarted exit 1, not restarted
an empty line restarted restarted exit 1, not restarted
future-input restarted restarted exit 1, not restarted
y or yes restarted restarted restarted
n exit 0 exit 0 exit 0
-r, </dev/null restarted restarted restarted
--force, </dev/null exit 0, not restarted restarted exit 1, not restarted
--force, y restarted restarted restarted

At EOF, Node's readline question never resolves, and the process exits 0 after the prompt. The old ask.rs comment said a closed stdin proceeds "exactly like Node", which was wrong for EOF, and I repeated it on #204. So at EOF, Rust was the one rerunning commands, and this change stops it. The departure from Node is the exit code at EOF, and declining every line but y/yes off a terminal.

Other prompts with the same shape. Only ask.rs has callers that prompt:

  • attach's Restart? [Y/n] is changed here.
  • restart's Session "<name>" is running. Kill and restart? [Y/n] (restart.rs:110) is left as it was. Measured with no terminal, pty restart <name> </dev/null stops a running session and starts it again. Node exits 0 and leaves the session running. A piped empty line restarts on both. That's a parity defect outside this decision, so I'll fix it separately if wanted.

The other stdin readers (metadata patch, operation reserve, readiness, the remote relay) read data, not answers.

Release note. pty attach no longer restarts a dead 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, without running the stored command. Any script or seat that relied on attach restarting a dead session non-interactively must pass -r now.

The repository has no release-notes file, since release.yml writes fixed notes. So this text is here and in decision 0020's migration section, for whoever cuts the next release.

Tests.

  • Conformance, crates/pty-conformance/tests/attach_no_restart.rs, with no terminal:
    • a_restart_prompt_at_eof_declines: </dev/null.
    • a_restart_prompt_off_a_terminal_needs_a_yes: a piped empty line, then relayed input.
    • a_piped_yes_restarts
    • auto_restart_needs_no_terminal: -r with </dev/null.
    • force_is_not_an_answer_to_the_restart_prompt: --force at EOF declines, then --force with a piped y restarts.
    • Each decline also checks that the stored command didn't run and that the exited record is still there.
  • The existing terminal cases are unchanged: default_policy_prompts_and_restarts and auto_restart_relaunches_without_prompting.
  • Unit test, ask::tests::consent_needs_an_answer_and_off_a_terminal_a_yes: each answer, at a terminal and off one.
  • divergences.toml records the three tests that fail on Node as intended, citing decision 0020. The parity table gets a row, and the README a paragraph.

Measured on Linux at 5896ff36, based on main ddfcf093:

  • scripts/conformance-both.sh attach_no_restart against the pinned Node build on Node 22.23.2. Node: 10 passed, 3 failed. This branch: 13 passed, 0 failed. Exactly the three ledger entries differ, and check-divergences.py matches them. Its other stale reports are entries for files this run didn't include.
  • Control: main's binary on this branch's attach_no_restart fails the three decline tests and passes a_piped_yes_restarts and auto_restart_needs_no_terminal.
  • cargo test -p pty-conformance against this branch's binary: 693 passed, 0 failed.
  • cargo test -p pty: 363 passed, 1 failed. The failure is interactive::typing_filters_the_list_and_escape_clears_it, which fails on main in my checkout too: the picker's fuzzy filter matches the checkout path. It passes in CI.
  • With no terminal, this branch's binary gives the "Rust now" column above, and pty restart gives the same results as main.

attach on a dead session asks Restart? [Y/n] and read stdin with no
terminal check. EOF read as an empty answer, which restarts, so a caller
without a terminal reran the stored command although nobody agreed to it.
--force did the same.

The prompt now counts only an answer someone gave (decision 0020):

- 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, attach exits 1, keeps the exited record and names -r. A
lone n still exits 0, -r still restarts without asking, and --force
still answers nothing.

The Node tool restarts nothing at EOF either, but it exits 0 there, and
off a terminal it restarts on any line but n. The conformance ledger
records the three new tests that fail on Node as intended.

pty restart's Kill and restart? prompt is unchanged. At EOF it still
stops a running session and starts it again, where Node leaves the
session running.
@myobie

myobie commented Nov 4, 2026

Copy link
Copy Markdown
Contributor Author

Independent read GO at exact head 5896ff36b1a3faf99056a7beb9a2cdafffa54611. All 9 checks are SUCCESS on this head and the PR is CLEAN.

Read:

  • attach on a dead session shows its last lines and asks Restart? [Y/n]. Before this change, stdin at EOF or not a terminal counted as "yes", so a non-interactive caller reran the stored command without ever consenting. The prompt now counts only an answer someone gave.
  • At a terminal nothing changes: any line but a lone n restarts, an empty line included; the one difference is that Ctrl+D (EOF) now exits 1. Off a terminal, only y or yes (any case) restarts. A lone n still exits 0.
  • With no answer, attach ends the prompt line, prints that the session was not restarted and that -r restarts it without asking, exits 1, and keeps the exited record. -r and --no-restart are unchanged, and --force answers nothing.
  • Correction recorded by the author: pinned Node, run with no terminal, does not read EOF as yes either. It restarts nothing there and exits 0, and it restarts on any piped line but n. So the departure from Node is exit 1 at EOF and y/yes-only off a terminal. It is written up in decision 0020, a parity row and three intended divergence entries.
  • Tests (conformance, run on both sides): EOF declines; a non-terminal empty line and relayed input decline; a piped y restarts; -r restarts with no terminal; --force at EOF declines and --force with y restarts. The author reports the three decline tests fail on the previous binary.
  • No wire-format change and no tier-1 file (events.jsonl, <id>.json, docs/disk-layout.md).

Release note (behavior change): pty attach no longer restarts a dead session when the restart prompt gets no answer. That covers stdin at EOF, and stdin that is not a terminal unless it gives y or yes; it exits 1 instead, without running the stored command. Any script that relied on attach restarting a dead session non-interactively must pass -r now.

Risks (low):

  • At a terminal, typing the word no still restarts, because only a lone n declines (Node parity). A possible follow-up.
  • pty restart keeps its own Kill and restart? [Y/n] prompt with the old behavior; a separate change will cover it.
  • A repo-wide search of the fleet's own sources found no caller that relies on a non-interactive restart.

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.

attach restarts a dead session without consent when stdin is not a TTY (EOF defaults to yes)

1 participant