Skip to content

fix(node): an old uv no longer skips the index; the port check is an install's - #965

Merged
LivXue merged 2 commits into
EverMind-AI:mainfrom
Handsome-wzw:fix/node_install_followups
Oct 16, 2026
Merged

LivXue merged 2 commits into
EverMind-AI:mainfrom
Handsome-wzw:fix/node_install_followups

Conversation

@Handsome-wzw

@Handsome-wzw Handsome-wzw commented Oct 15, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Three follow-ups to #963 (installing a Raven node on a registered machine),
from LivXue's review of it. Only raven/node/install.py and its test change.

  • An old uv no longer skips the configured index. fix(node): index off command lines, a bad port in one sentence, the node's own version #963 hands uv the index
    pip is configured with on the machine through UV_DEFAULT_INDEX, which uv
    reads since 0.4.23 (uv's changelog adds it there together with
    --default-index). A machine's own uv older than that ignores the variable
    and installed from PyPI while the plan named the mirror -- before fix(node): index off command lines, a bad port in one sentence, the node's own version #963 such a
    uv refused the --default-index argument instead, loudly. The probe now
    reports the found uv's version (UV_VERSION, from uv --version). With an
    index configured, a uv older than 0.4.23, or one that does not say its
    version, is passed over: the pinned uv is fetched under the node dir as for
    a machine with none, and the plan says why ("the machine has uv 0.4.22, and
    only uv 0.4.23 or later installs from the index pip is configured with").
    With nothing to fetch it with, the install says so in one sentence naming
    the version. Without an index configured an old uv is used as before.
  • The port check is an install's, not the look's. fix(node): index off command lines, a bad port in one sentence, the node's own version #963 refused an index
    whose port is not a number from 0 to 65535 in plan(), which install-node
    calls before it reads whether the node is already there and before
    --prune; neither needs the index, so a machine that had its node and a bad
    port in pip's configuration could not be told so or pruned. The check is
    now in refusal(), with the same sentence; the look takes the machine as it
    is.
  • The real install test no longer reaches the network. Its run
    stand-in stood in for uv pip install; since fix(node): index off command lines, a bad port in one sentence, the node's own version #963 the two steps that run it
    go through push when an index is configured, and the push stand-in ran
    them for real -- on a computer with a system-wide pip.conf the test
    installed packages, against the module docstring's promise. The push
    stand-in now stands in for them too, and the test also runs with an index
    configured, checking both steps are fed it on stdin.

Type

  • Fix
  • Feature
  • Docs
  • CI / tooling
  • Refactor
  • Other

Verification

Follow-up commit, after the review of this PR:

  • A uv an earlier install fetched under node_dir/.uv is used in place of a
    machine uv that would ignore the index. The probe reports that uv on its
    own (NODE_UV, NODE_UV_VERSION) beside the one it found first, and
    plan() swaps it in when the first is older than 0.4.23 (or silent) and
    the fetched one is not; without an index the machine's own uv serves as
    before, and one under node_dir that is itself too old does not help. Two
    tests failed first: the plan from such a probe (uses the fetched uv, no
    uv step, 60 MB, no refusal without curl), and the probe under a real
    /bin/sh reporting both uvs (then the one, then none).

  • The machines guide, English and Chinese: the "What lands on the machine"
    row for uv names the old-uv case, and the troubleshooting table has a row
    for the sentence an old uv earns. tests/test_docs_site.py and
    tests/test_living_docs.py pass (18), mkdocs build --strict passes.

  • The docstring of test_a_plan_whose_index_port_cannot_be_read_still_describes_itself_and_its_failures
    says the install, not the look, refuses the port. The real install test's
    uv_version is pinned to UV_VERSION rather than read off this computer's
    uv, so a test host with a uv older than 0.4.23 does not reach astral.sh
    through the stand-in that stubs only pip install.

  • tests/test_node_install.py -- 82 passed; with the cli, bundle, client and
    import-cycle files -- 204 passed; .venv/bin/python -m pytest -n 6 -- 31781
    passed, 3 failed (the libreoffice and two simulation cases, which fail
    the same way on main on this computer); diff coverage against main 100%
    (30/30 changed executable lines).

  • Real machine (a registered Linux machine; everything under this check's
    own directory, removed after): uv 0.4.22 from uv's release page was put
    first on the PATH the check's own runner gives the probe, with an index
    configured. Before this commit: the first install passed it over and
    fetched uv 0.13.0 under node_dir (85 MB planned), and the second look
    found 0.4.22 first again, planned to fetch 0.13.0 again (85 MB, an uv
    step), and with curl and wget taken away was refused with "upgrade uv
    there" while 0.13.0 sat under node_dir. After: the same first install,
    then the second look found the fetched 0.13.0, planned 60 MB with no uv
    step, and went ahead without curl or wget.

  • .venv/bin/python -m pytest tests/test_node_install.py -- 80 passed. Each
    new or changed case failed on main first: with an index configured, the
    real install test's packages step really ran uv pip install against the
    test's mirror and failed resolving it (now stood in for, both steps fed the
    index on stdin); plan() of a machine whose index names a port that is not
    a port now succeeds, refusal() gives the sentence, install() runs
    nothing, and a machine that has this node is found so and pruned; for a uv
    at 0.4.23 and later, older, and silent about its version, with and without
    curl or wget, the plan uses the machine's uv or fetches the pinned one and
    says why, or refusal() gives the exact sentence; an old uv still serves a
    machine with no index; under a real /bin/sh the probe reports the version
    a stand-in uv prints and nothing for one that prints none.

  • .venv/bin/python -m pytest tests/test_node_install.py tests/test_cli_ops_connection_commands.py tests/test_node_bundle.py tests/test_node_client.py tests/test_import_cycle_budget.py -n 6
    -- 202 passed.

  • .venv/bin/python -m pytest -n 6 -- 31780 passed, 3 failed
    (test_rpc_files.py::test_a_host_without_libreoffice_says_so and the two
    simulation cases, which fail the same way on main on this computer); diff
    coverage against main 100% (26/26 changed executable lines).

  • ruff check, ruff format --check, ty check raven/node/install.py
    clean; check_commit_messages.py, check_source_language.py and
    check_large_files.py on origin/main..HEAD clean.

  • Real machine (a registered Linux machine whose pip is configured with an
    HTTPS mirror; this check's own node dir, removed after): uv 0.4.22 from uv's
    release page was put where the probe finds a uv, and the install was given
    an index pointed at a port nothing listens on, so whether uv used it shows.
    With main's code the old uv was used and the install succeeded -- uv went to
    PyPI behind the plan. With this change the probe read 0.4.22, the plan
    fetched uv 0.13.0 and said why, the packages step failed with "Connection
    refused" (the index was used), and the node dir held uv 0.13.0 after.

  • Relevant tests pass locally

  • Relevant lint / type checks pass locally

  • User-facing docs or screenshots are updated when needed

The install module's docstring now says an old uv with an index configured
is passed over; the new Plan field and the moved check are documented
where they are. CONTEXT.md (the node entry) does not describe uv versions or
the index and still holds.

Risk

A machine whose own uv is older than 0.4.23 and whose pip is configured with
an index now gets the pinned uv fetched under the node dir (about 25 MB, as
for a machine with no uv), or one sentence asking to upgrade uv when it has
no curl or wget; every other machine is unchanged. A machine with a bad port
in pip's configuration can again be told it has the node and be pruned; an
install there is still refused with the same sentence. The change is to the
install plan and one probe line; rollback is a plain revert.

  • Security impact considered
  • Backward compatibility considered
  • Rollback path is clear for risky changes

Related Issues

Follows the review of #963 (the observations F1, F2 and F3 left on it).

…install's

Three follow-ups to EverMind-AI#963, from its review.

A machine's uv older than 0.4.23 ignores UV_DEFAULT_INDEX (uv's changelog
adds it in 0.4.23, with --default-index) and installed from PyPI while the
plan named the mirror pip is configured with there; before EverMind-AI#963 such a uv
refused the --default-index argument instead. The probe now reports the
found uv's version, and with an index configured a uv older than 0.4.23,
or one that does not say its version, is passed over for the pinned uv
fetched under the node dir; with nothing to fetch it with, the install
says so in one sentence naming the version. Without an index an old uv is
used as before.

The check that refuses an index whose port is not a number from 0 to
65535 ran in plan(), before install-node reads whether the node is already
there and before --prune, neither of which needs the index. It is now in
refusal(), with the same sentence, so such a machine is still found to
have its node and can be pruned.

The real install test stood in for uv pip only when a step went through
run; since EverMind-AI#963 the steps that run uv pip go through push when an index is
configured, and with a system-wide pip.conf on the computer running it the
test really installed packages. The push stand-in now stands in for them
too, and the test runs with an index configured as well.

Co-authored-by: Claude (claude-opus-5-5) <noreply@anthropic.com>
@Handsome-wzw
Handsome-wzw requested a review from LivXue October 15, 2026 08:28

@gloryfromca gloryfromca left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No blockers; this can merge as far as I am concerned.

Reviewed github/main...HEAD, including the stacked MCP lending and DAG sandbox changes and the tip install follow-up. I checked the repository rules and context vocabulary, the changed code and tests, the install caller and probe/install/prune ordering, relevant history, compatibility at the node protocol boundary, credential handling, and whether tests were weakened. The uv 0.4.23 boundary also matches uv's documented introduction of UV_DEFAULT_INDEX.

Verification:

  • uv run --all-packages pytest tests/test_node_install.py tests/test_node_bridge.py tests/test_node_client.py tests/test_node_server.py tests/test_subagent_acp_mcp_lifecycle.py tests/test_subagent_acp_remote.py tests/test_subagent_dag_core.py tests/test_subagent_dag_runner.py tests/test_subagent_manager.py tests/test_subagent_third_party.py tests/test_agent_loop_playbook_entry.py tests/test_cli_playbook_commands.py -x - 1369 passed, with one asyncio resource warning.
  • The same default uv run pytest ... invocation initially stopped in the autouse fixture because raven_everos was not installed; enabling all workspace packages resolved the test environment without changing or skipping tests.
  • scripts/check_large_files.py github/main..HEAD - passed.
  • scripts/check_source_language.py github/main..HEAD - passed.
  • git diff --check github/main...HEAD - passed.

@LivXue

LivXue commented Oct 15, 2026

Copy link
Copy Markdown
Member

No blockers in the reviewed scope.

Scope: I reviewed only the raven/agent/** portion of this PR -- 0 of 2 changed files. Neither raven/node/install.py nor tests/test_node_install.py is in that scope, so this verdict covers no code. The observations below were read and reproduced as context; they are filed here because no line of theirs can carry an inline note, and none of them blocks.

What I examined: the follow-up to the observations F1, F2 and F3 I left on #963. With an index configured, a machine's uv older than 0.4.23 (UV_INDEX_SINCE), or one that does not say its version, is now passed over: uv 0.13.0 is fetched under node_dir/.uv instead, or, with no curl or wget, the install is refused in one sentence. The port check moved from plan() into refusal(), and the real-install test's push stand-in now stubs the pip install steps as its run stand-in did. Each of the three does what it was written for (see Verification), and the first brings the observation F1 below.

Observations outside the reviewed scope, numbered for this PR (the ones from #963 are named with its number):

  • F1, Nonblocking -- a uv this install fetched is not found again while the machine's old uv is there. The probe stops at the first executable uv (raven/node/install.py:226-228), and "$dir/.uv/uv" comes last, after the PATH, ~/.local/bin and ~/.cargo/bin. On a machine with uv 0.4.22 on its PATH and an index configured, the first install passes it over (:105, :110) and fetches uv 0.13.0 into node_dir/.uv (:320-332). The next install-node, which the guide asks for after every Raven update (docs-site/docs/machines.md:116-119), finds uv 0.4.22 first again and fetches and installs uv 0.13.0 a second time: the plan counts 25 MB more space, the machine needs to reach uv's installer at astral.sh on every update, and the plan's reason names only the machine's uv 0.4.22. If the machine has no curl or wget by then, refusal() asks to "upgrade uv there" while uv 0.13.0 sits in node_dir/.uv. The guide says "the cached uv and Python stay" (machines.md:118-119), and the comment at raven/node/install.py:422-423 says "the uv and the Python put there are reused by the next attempt". On the merge base the same machine used its old uv, which is fix(node): index off command lines, a bad port in one sentence, the node's own version #963's F3, so this comes with the route this PR chose. One direction: let the plan see node_dir/.uv/uv even when a uv ahead of it is passed over, and use it; how is yours to choose.
  • F2, Nonblocking -- the user guide does not follow. The table under "What lands on the machine" says uv is fetched to <node_dir>/.uv "when the machine had none" (docs-site/docs/machines.md:97, docs-site/docs/machines.zh.md:81), which is no longer the only case, and the troubleshooting table has a row for "has no uv, and no curl or wget to fetch one with" (machines.md:373, machines.zh.md:303) but none for the new sentence, "has uv 0.4.22, and only uv 0.4.23 or later installs from the package index pip is configured with there". The description checks CONTEXT.md and leaves "User-facing docs" unchecked; the English and Chinese pages carry the same two lines.
  • F3, Nonblocking -- the docstring of test_a_plan_whose_index_port_cannot_be_read_still_describes_itself_and_its_failures (tests/test_node_install.py:527) still says "The look refuses such an index"; after this PR the look does not, as the new tests at :419 and :447 say.

Verification:

  • F1: I imported this head's git archive tree (its __file__ printed) and ran probe_script() and plan() under a real /bin/sh, with a stand-in uv on the PATH answering uv 0.4.22 (5b9c1fa1c 2024-10-16), another at ~/.raven-node/.uv/uv answering uv 0.13.0, and PIP_INDEX_URL set. With curl, uv_from is fetched, the steps begin with uv, and the plan needs 85 MB; without curl, refusal() asks to upgrade uv. Controls: with no old uv on the PATH, the probe finds node_dir/.uv/uv, uv_from is machine and there is no uv step; with the old uv and no index, uv_from is machine.
  • UV_INDEX_SINCE: with uv's own 0.4.22 and 0.4.23 release binaries for x86_64 Linux, I ran the virtualenv and packages steps this head's steps() writes, under /bin/sh, with a dead index http://127.0.0.1:9/simple on stdin. 0.4.22 ignored it and installed the packages anyway (rc 0); 0.4.23 used it and failed with Connection refused (rc 2). The probe's version line reads 0.4.22 and 0.4.23 from those binaries, and 0.11.25 from this host's uv, whose version line ends with its platform in parentheses.
  • fix(node): index off command lines, a bad port in one sentence, the node's own version #963's F2: with a probe that reports this node present and an index on a bad port, plan() succeeds and the prune runs. The CLI calls refusal() before it describes or installs (raven/cli/ops_connection_commands.py:271-302), and install() calls it first as well, so no path to an install lost the check.
  • fix(node): index off command lines, a bad port in one sentence, the node's own version #963's F1: tests/test_node_install.py on this head's archive: 80 passed. In the real-install test's case with an index, both uv pip steps reached the push stand-in with the index on stdin. This host has no system-wide pip configuration, so the merge base's test would not reach the network here either; I did not re-run the old behaviour.
  • F2 and F3 are about text; the lines are quoted above.

Checked and not reported: the index still travels only on stdin, uv --version's stderr is discarded, and the two new sentences about an old uv carry its version, the installer's URL and the machine's label, with no address or sign-in. A version that is not three numbers, such as 0.5.0+3 or 0.4.23-rc1, reads as "a uv that does not say which version it is", which is passed over when an index is configured: the safe direction. The real-install test takes uv_version from this host's uv (tests/test_node_install.py:989-990), so on a host whose uv is older than 0.4.23 its case with an index would try to fetch uv through the run stand-in, which stubs only pip install (:962-967), and reach for astral.sh; that needs a test host with a uv from October 2024 or earlier, and this host's is 0.11.25.

… the old-uv case

Follow-ups to EverMind-AI#965's review.

The probe stopped at the first uv it found, and node_dir/.uv/uv came
last. So on a machine whose own uv is older than 0.4.23 with an index
configured, the first install passed that uv over and fetched uv 0.13.0
under node_dir, and the next install-node, which every Raven update asks
for, found the old uv first again and fetched 0.13.0 a second time -- 25
MB more in the plan and a round trip to astral.sh on every update; and
with no curl or wget by then it refused, asking to upgrade uv, while a
uv that reads the index sat under node_dir. The probe now reports the uv
under node_dir on its own as well, and the plan uses it in place of a
machine uv that would ignore the index, so nothing is fetched twice and
the uv and Python put there are reused by the next attempt, as the
comment and the guide say.

The machines guide said uv is fetched only when the machine had none,
and its troubleshooting table had no row for the sentence an old uv
earns; both pages now have them. A test docstring still said the look
refuses a bad port; it says the install does. The real install test
takes its uv as recent enough rather than reading this computer's: with
a uv older than 0.4.23 here, its case with an index would have fetched
one through a stand-in that stubs only pip install.

Co-authored-by: Claude (claude-fable-5-1) <noreply@anthropic.com>
@Handsome-wzw

Copy link
Copy Markdown
Contributor Author

All three, and the test-host point from your "checked and not reported", are in 2f94f35.

  • F1: the probe now reports the uv under node_dir/.uv on its own (NODE_UV, NODE_UV_VERSION) beside the first one it finds, and plan() uses it in place of a machine uv that would ignore the index -- when the first is older than 0.4.23 or silent and the fetched one is not. So the second install-node plans 60 MB with no uv step, fetches nothing, and goes ahead without curl or wget; the comment at the end of install() and the guide's "the cached uv and Python stay" hold again. Without an index the machine's own uv serves as before, and a uv under node_dir that is itself too old does not help. Both new tests failed on 390c970 first. On the real machine, with uv 0.4.22 placed ahead on a PATH the check's own runner gives the probe: before, the second look planned to fetch 0.13.0 again (85 MB) and, with curl and wget taken away, refused with "upgrade uv there" while 0.13.0 sat under node_dir; after, it found the fetched 0.13.0, planned 60 MB with no uv step, and went ahead without them.
  • F2: both pages of the machines guide now say uv is also fetched when the machine's own is older than 0.4.23 while pip there is configured with an index, and the troubleshooting table has a row for that sentence. mkdocs build --strict and the docs tests pass.
  • F3: the docstring says the install refuses the port, not the look.
  • The real install test's uv_version is pinned to UV_VERSION rather than read off the test host's uv, so a host with a uv older than 0.4.23 does not reach astral.sh through the stand-in that stubs only pip install.

Full suite: 31781 passed, the 3 failures are the ones that fail the same way on main on this computer; diff coverage 100% (30/30); tests/test_node_install.py 82 passed, docs tests and mkdocs build --strict pass.

@gloryfromca gloryfromca left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No blockers; this can merge as far as I am concerned.

Reviewed the delta from 390c970612e4 and checked it against the full target diff, install caller, probe/plan/steps behavior, relevant history, repository rules and context terms, backward compatibility, and the prior observations. The cached node-local uv is selected only when an index is configured, the first uv is too old or silent, and the cached uv is new enough; no-index and unusable-cache behavior remain unchanged. The documentation and test-host follow-ups match the implementation, and I found no weakened assertion or new concrete failure.

Verification:

  • uv run --all-packages pytest tests/test_node_install.py -x - 82 passed.
  • uv run --all-packages pytest tests/test_docs_site.py tests/test_living_docs.py -x - 18 passed.
  • uv run --frozen --python 3.12 --group docs mkdocs build -f docs-site/mkdocs.yml --strict - passed (dependency SyntaxWarnings only).
  • scripts/check_large_files.py github/main..HEAD - passed.
  • scripts/check_source_language.py github/main..HEAD - passed.
  • git diff --check github/main...HEAD - passed.

The first direct docs-build attempt used an environment without the docs dependency group and could not find mkdocs; rerunning through the repository's documented docs group passed.

@LivXue

LivXue commented Oct 15, 2026

Copy link
Copy Markdown
Member

Windows compatibility review at 2f94f359f86342c124e88e9a6387185f1d091a9b, against base d102810be308aaccb5d9b55c2341e4b3bb4238ff.

No new Windows compatibility issue found in the inspected scope. Reviewed all four changed files, the CLI's plan/refusal/install/prune ordering and the SSH run/push callers. The added awk, executable probes and POSIX quoting belong to the remote machine's shell; they are not run by PowerShell/cmd on the Windows host. Index bytes still travel as UTF-8 on stdin. The cached-uv selection and relocated port validation are covered by the native host tests.

Native Windows / Python 3.13.12: 85 passed, 17 skipped, 18 deselected in the focused install/CLI/bundle selection. Sixteen skips require a POSIX shell; one requires symlink privileges this account lacks. An initial broader run stopped after 79 passes and 15 skips at test_adding_an_ssh_machine_writes_a_host_alias: its fixture sets HOME, while the Windows path lookup uses USERPROFILE, so its expected SSH config is absent. The identical assertion fails on the exact base; this is not an install regression.

Exact-head detached worktree; fresh uv sync --frozen --python 3.13 --all-packages --extra dev, isolated home/temp paths, PYTHONUTF8=1, UV_NO_SYNC=1:

$R = 'C:/Users/livxu/AppData/Local/Temp/raven-windows-review/c8dec77ada644245ae6438b441d39c59'
uv run pytest tests/test_node_install.py tests/test_cli_ops_connection_commands.py tests/test_node_bundle.py -k "node or install or prune" -n 0 -x --tb=short --basetemp "$R/checks/965-focused/pytest" --junitxml "$R/checks/965-focused/junit.xml"
# Exact base, same locked environment with base source imports: same FileNotFoundError
uv run pytest tests/test_cli_ops_connection_commands.py::test_adding_an_ssh_machine_writes_a_host_alias -n 0 -x --tb=short --basetemp "$R/checks/965-base-alias/pytest" --junitxml "$R/checks/965-base-alias/junit.xml"

git diff d102810be308aaccb5d9b55c2341e4b3bb4238ff..2f94f359f86342c124e88e9a6387185f1d091a9b --check passed. No real remote install, download/prune, docs build or full-suite run was performed.

@LivXue
LivXue merged commit baf7633 into EverMind-AI:main Oct 16, 2026
42 of 44 checks passed
Handsome-wzw added a commit that referenced this pull request Oct 17, 2026
… read (#977)

## Summary

Follow-up to LivXue's review note on #957 (the pick-up's binding at
`cli_agent.py:1393`). The machine's daemon kept an agent's stderr in the
task's record (`stderr.log`) but sent it only to the connection holding
the
task when a line was written, and `agent/attach` replayed `frames.jsonl`
alone. So what an agent said only on stderr while no connection held it
never
reached the host:

- **The session id a CLI names only on stderr** (hermes does). The
pick-up
#957 added binds a create's handle to the id in its output; for such a
CLI
it found none, warned "not resumable", and the next turn to the instance
  opened a new session.
- **The reason a command failed.** A run picked up after this computer
  stopped was reported as `exited 3` where the dispatch would have said
  `exited 3: token expired`. This part predates #957.
- **The stderr written while a link was down**, on a run still being
  watched, the same way.

The fix is in the node and the host's link to it (`raven/node/`):

- The daemon numbers stderr lines as it numbers frames (`agent/stderr`
  carries `n`), and `agent/attach` takes an optional `stderr_after`: the
stderr lines past that one are replayed after the frames, under the lock
that forwards a new line, so none is sent twice or missed at the
handover.
It works the same for a live task, a finished one, and a record a daemon
that never ran the task reads. An attach that does not ask hears no
stderr,
as before; a `stderr_after` that is not a whole number is refused like a
  bad `after`.
- The host's `ChannelEndpoint` counts the stderr lines it got
(`stderr_received`). A cli pick-up (`NodePool.attach`, a process that
read
none of them) asks from the first line; an acp pick-up from the line the
  run's record kept as the prompt went out (`RemoteRun.stderr_received`,
  follow-up commit below); a re-attach after a lost link
  (`NodePool._reattach`) asks from the last one it read.

The cli dispatch and pick-up read stderr off the endpoint as before and
now
get all of it, so the reason and the session id are there. An acp
pick-up's
connection drains the same endpoint into the stderr tail it quotes in
its
errors and into its journal, so what the agent wrote during the turn
while
this computer was away reaches them too; the acp run record keeps the
stderr
line count as the prompt goes out, so the earlier prompts' lines are not
read again (follow-up commit below). No protocol bump:
the new fields are optional, and a node runs exactly the host's code
anyway
(protocol and digest are checked at every connection).

## Type

- [x] Fix
- [ ] Feature
- [ ] Docs
- [ ] CI / tooling
- [ ] Refactor
- [ ] Other

## Verification

- `tests/test_node_stderr_replay.py`, new, 22 tests: the daemon numbers
and
keeps stderr lines; an attach replays exactly the lines past
`stderr_after`
(from a live task, a finished one, and a record a daemon that never ran
it
reads), none when not asked, none from a record with no `stderr.log`,
and
refuses a bad `stderr_after`; the endpoint counts by the node's numbers;
a
  re-attach and a pick-up ask from the right line. End to end on the
stand-in machine (real daemon, stand-in ssh): a left cli run's reason on
stderr reaches its pick-up (said before and after this computer
stopped),
a left create that names its session only on stderr is bound to it by
the
  pick-up (both orders), and a reason written while the link was down
reaches the run watching it. 18 fail on main (2c27181); the 4 that
pass
  there pin what does not change (no `stderr_after`, past the last line,
  past the end, no `stderr.log`).
- Changed assertions: the four attach-parameter assertions in
  `tests/test_node_client.py` and the forwarded-stderr one in
  `tests/test_node_server.py` include the new fields.
- Full suite: 32065 passed, 173 skipped, 2 xfailed, 3 failed -- the same
3
that fail on main (2c27181) on this computer when run alone there: a
host
  without LibreOffice, and two simulation cases.
- Diff coverage: 100.00% (30/30 executable changed lines; `coverage
json`, then
`scripts/coverage_gate.py diff --base-ref origin/main --threshold 90`).
- `ruff check`, `ruff format --check`, `ty check`, `lint-imports`,
  pre-commit on the diff; `scripts/check_commit_messages.py`,
`scripts/check_source_language.py` and `scripts/check_large_files.py` on
  `origin/main..HEAD`: clean.
- Docs: CONTEXT.md's Raven node entry, the protocol module's notes, and
the
machines guide's "Disconnects and restarts" section in English and
Chinese
now say the daemon keeps and replays stderr; the changelog has an entry
under Fixed. `tests/test_docs_site.py` and `tests/test_living_docs.py`
pass
  (18), `mkdocs build --strict` passes.
- Real machine, ravenx2, an isolated node directory per tree. The host
process that dispatched was killed with SIGKILL and the run picked up by
a
new process; the link was dropped by killing the local ssh. The command
  writes `token expired` (or `session slow-A1`) on stderr:

  | Probe | main (2c27181) | this branch |
  |---|---|---|
| reason written after the host process was killed | `exited 3` |
`exited 3: token expired` |
| reason written before it was killed | `exited 3` | `exited 3: token
expired` |
| reason written while the link was down | `exited 3` | `exited 3: token
expired` |
| create left before its commit, session id only on stderr | "not
resumable", nothing bound | bound to `slow-A1`, no warning |

The machine was cleaned after: the probe's two daemons ended by pid, its
  directory removed.
- The runs above are on main 2c27181 (#957 merged). The branch was
then
  rebased onto 400b30d, whose two new commits touch only TUI copy and
i18n; the node, docs and range checks were run again there (238 passed,
1
skipped). It merges cleanly with the open #964 and #965 (`git
merge-tree`).

- [x] Relevant tests pass locally
- [x] Relevant lint / type checks pass locally
- [x] User-facing docs or screenshots are updated when needed

- Follow-up commit e0468fa, after the agent review's F1 (an acp
pick-up asked
for the stderr from the agent's first line, across every earlier prompt
of
its process): `RemoteRun.stderr_received`, taken in
`Tracker.prompt_sent`
  off the endpoint's count, and passed through `NodePool.attach(...,
stderr_after=...)` by the pick-up; a cli pick-up keeps asking from 0; a
  record from before the field reads with 0. Regression on the stand-in
machine
(`test_a_pick_up_reads_the_turn_s_own_stderr_and_not_what_the_prompts_before_it_wrote`):
two turns of 150 stderr lines, a third interrupted after its prompt, the
next start's pick-up -- before the change its tail held 200 lines, 50 of
them the second turn's; after it, the third turn's 150 and nothing else.
Unit tests pin the record, the pick-up's attach arguments and the
default
of 0, and the tracker's wiring. The related files 259 passed; full suite
32067 passed with the three failures this Mac always has, plus one test
whose stand-in `Tracker` lacked the new keyword, fixed in the same
commit.
  Diff coverage of the changed lines: see CI.

## Risk

The replayed lines are the agent's own stderr, already kept in the
task's
owner-only record and already sent live to whichever connection held the
task; nothing new leaves the machine. `stderr_after` and `n` are
optional: an
attach without `stderr_after` behaves as before, and a line without a
number
counts on from the last. A cli pick-up replays the whole `stderr.log`,
as it
already replays every frame of the command; an acp pick-up replays the
lines
past the count its record kept as the prompt went out, so the replay is
bounded to the turn, and the connection keeps a bounded tail of it.
Rollback
is reverting the commit.

- [x] Security impact considered
- [x] Backward compatibility considered
- [x] Rollback path is clear for risky changes

## Related Issues

Follow-up to #957, for this review note:
#957 (comment)

---------

Co-authored-by: Claude (claude-opus-5-5) <noreply@anthropic.com>
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.

3 participants