Skip to content

feat(*): upgrade from the page with progress, open it for bare raven - #806

Merged
0xKT merged 16 commits into
mainfrom
feat/page_upgrade_and_entry
Sep 26, 2026
Merged

0xKT merged 16 commits into
mainfrom
feat/page_upgrade_and_entry

Conversation

@arelchan

Copy link
Copy Markdown
Contributor

Summary

The upgrade button on the page could never work for a normal install, and when an upgrade did run, neither the page nor (on a slow link) the terminal showed anything that moved. This PR makes the page upgrade work, shows its progress, and fixes what an end-to-end run of it turned up, plus two entry-point changes from the same pass.

The page upgrade works on raven web. raven web runs raven gateway, so nearly every page is gateway-hosted, and system.upgrade refused all of them (gateway_hosted): its relaunch brought back a bare raven serve, which builds no ChannelManager, so the IM channels would have vanished. Now:

  • On every page mount the gateway hands the page its own graceful stop (the path gateway.shutdown takes), its busy check, and its supervisor pid. Every mount, because a config swap tears the page down and disarms it.
  • On upgrade the gateway exits 0. The raven web supervisor already reads a zero exit as "stand down" (its docstring names system.upgrade), so it exits too.
  • The helper waits for the supervisor, not the gateway, then installs and relaunches raven web --supervise on the same port with the same token and cookie. Waiting on the supervisor matters: its cleanup removes web.json after the gateway exits, and a supervisor started before that would lose its state file, inviting the next raven web to start a second one.
  • Refused, each with its own reason, when nothing would bring the gateway back (started by hand, or raven web --foreground), when work the page cannot see is running (an IM turn, a sub-agent, a pending question), or when there is no executable to relaunch from. The config swap and the upgrade now share one busy check.

This takes effect for upgrades that start from a release containing it; a gateway on an older release still refuses with the old message.

The page shows the upgrade's progress. The card said "Upgrading Raven..." and nothing else until the new Raven answered, because the process that served the page is gone for the whole install.

  • The helper downloads the release itself, where the bytes can be counted, and hands uv the local files so uv only resolves dependencies. All or nothing: if any asset cannot be fetched, uv gets the original URLs and downloads them as before. Files live under ~/.raven/cache/upgrade/<version>; earlier versions' are cleared.
  • Once the old Raven has exited, the helper answers on the page's port (recorded in the upgrade marker): /upgrade/status with phase, bytes done and total, and rate; 503 to everything else, so the page's "is it back?" probe keeps waiting and a reload gets a self-refreshing wait page. It answers only the page's own Host, and lets the port go before the relaunch binds it.
  • A failure is held until the page has read it (at most five seconds). Before, a failed install relaunched the old version and the page reloaded onto it silently.
  • The page polls the status, measures the bar while bytes are counted and slides it otherwise, and says when the new Raven is starting. Its twenty-minute ceiling now counts silence, not the whole upgrade, so a slow download that is visibly moving is not given up on.

Two bugs the end-to-end run exposed, both fixed here:

  • A red "the service is not running, start it with raven web" bar during the upgrade. The transport's health probe counted any answer but 401/403 as "the gateway is back and refused this session", so the helper's 503 made it give up. A 5xx now means "not back yet". Following that bar's advice mid-upgrade is exactly what would start a second supervisor.
  • The old gateway took 46 seconds to exit, blocked on the page's own open WebSocket. aiohttp's cleanup waits for live handlers instead of closing websockets. The WS gateway now closes its sockets from on_shutdown; the same run dropped to 4 ms. This also speeds up raven web --stop with a page open.

raven gateway refuses a half-written install. raven serve has guarded this since the upgrade marker existed, but the gateway never did, and the gateway is what raven web supervises. A restart inside an upgrade window came up on the half-written tree and, because the page route is chosen once, served the placeholder page until restarted by hand. The guard runs before the instance lock. It is also a precondition for the restart above.

Refusals are localized. The page pasted the server's English detail into an otherwise Chinese card. Refusals it can name come from the catalogue; refusals whose detail carries facts from this run keep them. A server-side busy gets the same wait notice as the page's own busy turn, not the "run this in a terminal" card.

The terminal upgrade names its download size. uv draws one bar per package and no total, so a large release reads like a stalled install (a real report: a 72 MiB upgrade at about 50 KB/s looked frozen). The helper prints each asset's size and the total before downloading, from HEAD requests bounded at 5 seconds, headline first so a hanging probe adds no silence; the terminal then shows the helper's own live progress line. Output is flushed so it keeps its place in web.log.

Bare raven opens the page. The installer ends in raven web, but the command typed the next day landed in the TUI. Bare raven now does what raven web does; where no browser can be opened it runs the TUI instead, decided before anything starts. raven web and raven tui are unaffected.

Composer placeholders. Empty: "Describe your task, or have Raven build an agent for it" becomes "Describe your task". While a turn runs: "it is sent once this turn ends" was stale (mid-turn sends merge into the running turn; the queue is only a fallback), so it now says Raven receives the new instruction directly.

Type

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

Verification

End to end, three times, in a sandboxed uv tool install (own UV_TOOL_DIR and RAVEN_HOME, no plugins, so nothing reached a real memory server), upgrading 0.2.90 to 0.2.91 from a local stand-in for the GitHub release API, clicking the real rail-foot upgrade row in a browser:

  • First run (fast local download): page back after 17 seconds, reloaded by itself, still signed in; served by python -P -m raven gateway (not serve) on the same port; web.json names the new supervisor, which is the new gateway's parent; old supervisor and gateway gone.
  • Second run, with the release trickled at ~200 KB/s: the progress showed, and the run found the two bugs above (the red bar, and the old gateway taking 46 seconds to exit).
  • Third run, same trickle, after the fixes: the card read "Downloading the new version: 0.6 / 6.1 MiB, 191 KB/s" five seconds after the click, then 2.4 / 6.1 MiB with the bar measuring; no red bar; the old gateway's page socket closed 4 ms after the control plane stopped; uv prepared the package from the local file in 157 ms; the page reloaded itself onto 0.2.91.
  • raven upgrade in a real pseudo-terminal against the same trickle: the live line redrew in place 98 times from 0.1 to 6.1 of 6.1 MiB, then uv installed from the local file.

Commands, all on this branch rebased onto current main:

  • make coverage - 26,955 passed. 21 failed, and they are the same 21 that fail on unmodified main on this machine (research-tool and simulation tests, and one that assumes LibreOffice is absent); a 22nd was a load-sensitive test added here, fixed in this PR and then 25 of 25 under CPU load
  • scripts/coverage_gate.py diff --base-ref github/main --threshold 90 - 100% (77 of 77 changed lines)
  • npm test --prefix ui-web - 3096 tests pass
  • make lint-python - pass; uv run lint-imports - 10 contracts kept, 0 broken
  • make lint-ui - 0 errors (4 pre-existing exhaustive-deps warnings in files this PR does not touch); npm run lint:i18n --prefix ui-tui - up to date
  • node ui-web/scripts/check-page.mjs, check-css.mjs, check-class-namespace.mjs after npm run build and build.py - OK
  • mkdocs build --strict - pass
  • npx commitlint --from github/main --to HEAD, scripts/check_commit_messages.py, make check-source-language and make check-large-files with COMMIT_RANGE=github/main..HEAD - pass

New tests were mutation-checked; each edit below turns the test written for it red. Server: relaunching serve instead of the supervisor, waiting on the gateway instead of the supervisor, dropping the unsupervised refusal, the busy refusal, the no-relaunch guard, or the page-mount hand-over; not closing sockets on shutdown. Helper: never downloading, not closing the status server before the relaunch, dropping the Host check, not holding a failure, dropping the manifest or its flush. Page: counting the ceiling from the start instead of from silence, ignoring a held failure, dropping the "starting" line, a bar that ignores or overshoots the fraction, the old 5xx probe rule, server sentence instead of catalogue, busy routed to the failure card. Entry point: never opening the page, opening it on the wrong port.

Note for reviewers running the gates locally: make check-commits hardcodes --from origin/main, and check-source-language / check-large-files default COMMIT_RANGE to origin/main..HEAD. Where origin is not GitHub those ranges fail on unrelated commits; pass the GitHub base explicitly.

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

The quick-start's update section (English and Chinese) now covers updating from the page.

Risk

User-visible changes:

  • Bare raven opens the browser page instead of the TUI, leaving the resident gateway raven web always leaves. Headless machines keep the TUI; raven tui still names it outright.
  • The page upgrade restarts the gateway, briefly disconnecting IM channels. It is refused while any turn, sub-agent or pending question is running.
  • During a page-started upgrade the page's port is answered by the upgrade helper (status and 503s) instead of nothing.
  • The helper keeps the current version's wheels under ~/.raven/cache/upgrade/<version> (about 75 MB for a release with its engines), because the tool's receipt names them as the install source.
  • A raven gateway that starts inside an upgrade window waits it out and exits instead of serving; the supervisor restarts it once the install is whole.

Known edge: running raven web by hand in the seconds between the old supervisor exiting and the new one starting can start a second supervisor beside the relaunched one. The install itself stays protected by the upgrade marker, and the page no longer tells the reader to do this.

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

Security: the status endpoint is unauthenticated but loopback-only, answers only the page's own Host (so a rebound host name gets 421), and exposes version numbers and byte counts; the relaunch carries the same token and cookie the raven serve path already did. Rollback: revert the squash commit. Upgrades started from a release without this change keep the old refusal, so there is no mixed-version state to unwind.

Related Issues

N/A

arelchan and others added 14 commits September 27, 2026 00:02
`raven serve` has refused to start on a half-written environment since
the upgrade marker existed, but `raven gateway` never did, and the
gateway is what `raven web` supervises. A restart that landed inside an
upgrade window came up on the half-written tree, and because the page
route is chosen once, served the placeholder page until restarted by
hand. The guard now runs first in the gateway command, before the
instance lock, since the wait inside it can last the whole install.

The page also stops pasting the server's English refusal sentence into
an otherwise localized card. Refusals it can name come from the
catalogue; refusals whose detail carries facts from this run keep them.

Co-authored-by: Claude (claude-opus-5-5) <noreply@anthropic.com>
uv draws one progress bar per package and never an overall total, so an
upgrade whose engines are tens of megabytes reads exactly like one that
has stopped. On a slow link the wait is long enough that people kill it.

The helper now prints what it is about to fetch, with each asset's size
and the total, read from the release assets with HEAD. The headline is
printed before the probes so a probe that hangs adds no new silence, the
probes are bounded at five seconds, and a release that answers none of
them degrades to the headline alone rather than failing the upgrade.

Co-authored-by: Claude (claude-opus-5) <noreply@anthropic.com>
The installer ends in `raven web`, which opens the page, but the command
a reader types the next day landed them in the terminal UI instead. One
product, two entry points, two different surfaces.

Bare `raven` now does what `raven web` does. Where no browser can be
opened the page would be a URL nobody can reach, so it runs the native
TUI instead, through the same `tui` callback as before so the startup
gate is identical either way. The check is made before anything is
started, so a headless box never ends up with a resident gateway it
cannot show. `raven web` and `raven tui` name a surface outright and are
unaffected.

Co-authored-by: Claude (claude-opus-5) <noreply@anthropic.com>
The placeholder read "Describe your task, or have Raven build an agent
for it". The second half is dropped so the input asks for one thing.

Co-authored-by: Claude (claude-opus-5-5) <noreply@anthropic.com>
From the page, the upgrade helper's output goes to web.log, a file, which
Python buffers by the block, while uv writes to the same file at once. An
end-to-end run showed the manifest landing after uv's own lines, the one
place it exists to precede. The headline and the total are now flushed.
One asset now reads "1 package" rather than "1 packages".

Co-authored-by: Claude (claude-opus-5-5) <noreply@anthropic.com>
`raven web` runs `raven gateway`, so nearly every reader's page is
gateway-hosted, and `system.upgrade` refused all of them: its relaunch
brought back a bare `raven serve`, which builds no ChannelManager, so the
IM channels would have vanished. The upgrade button could never work.

The gateway now hands the page its own graceful stop, its busy check and
its supervisor pid on every page mount. On upgrade the gateway exits
cleanly, the `raven web` supervisor reads the zero exit as a request to
stand down, and the helper waits for the supervisor, not the gateway,
before installing and bringing back `raven web --supervise` on the same
port with the same credentials. Waiting on the supervisor matters: its
cleanup removes web.json after the gateway exits, and a new supervisor
started before that would lose its state file to the old one.

The upgrade is refused, with its own reason, when nothing would bring
the gateway back (started by hand, or `raven web --foreground`), when
work the page cannot see is running (an IM turn, a sub-agent, a pending
question), and when there is no executable to relaunch from. The swap
and the upgrade now share one busy check instead of two copies.

Verified end to end in a sandboxed uv tool install upgrading 0.2.90 to
0.2.91 from a local stand-in release: the page came back by itself in
17 seconds, still signed in, served by `raven gateway` on the same port.

Co-authored-by: Claude (claude-opus-5-5) <noreply@anthropic.com>
Both lived inside the gateway's run() closure, which serves forever and
so never executes under test: the busy check was covered by nothing and
the "only our own parent is our supervisor" rule only by a source-text
pin. They are now module-level functions with behavioural tests, and the
closure keeps one-line calls into them.

Co-authored-by: Claude (claude-opus-5-5) <noreply@anthropic.com>
aiohttp's cleanup waits for live handlers instead of closing websockets,
so a browser tab left open held the process up for most of a minute
after it was told to stop. Measured in a page-started upgrade: 46 seconds
between the control plane stopping and the tab's socket closing, during
which the upgrade could not start because it waits for this process to
exit. The WS gateway now closes every tracked socket with 1001 from the
app's on_shutdown hook; the same run dropped to 4 milliseconds. This also
speeds up `raven web --stop` whenever a page is open.

Co-authored-by: Claude (claude-opus-5-5) <noreply@anthropic.com>
The transport decides between "the gateway is absent, keep waiting" and
"the gateway is back and refused this session" by probing /health, and
it counted any answer but 401 or 403 as back. During a page-started
upgrade the helper holds the port and answers 503, so the transport gave
up and painted "the service is not running, start it with raven web":
the one command that, mid-upgrade, starts a second supervisor beside the
one coming back. A 5xx now means not back yet.

Co-authored-by: Claude (claude-opus-5-5) <noreply@anthropic.com>
The page's upgrade card said "Upgrading Raven..." and nothing else until
the new Raven answered, which on a slow link is tens of minutes with
nothing moving: the same complaint as the terminal, moved to the page.
The page had nothing to ask, because the process that served it is gone
for the whole install.

The helper now downloads the release itself, where the bytes can be
counted, and hands uv the local files so uv only resolves dependencies.
It is all or nothing: if any asset cannot be fetched, uv is given the
original URLs and downloads them as before. The files live under
~/.raven/cache/upgrade/<version>, and earlier versions' are cleared.

Once the old Raven has exited, the helper answers on the page's port,
recorded in the upgrade marker: /upgrade/status with the phase, bytes
done and total, and rate, and 503 to everything else, so the page's "is
it back?" probe keeps waiting and a reload shows a self-refreshing wait
page. It answers only the page's own Host, and lets the port go before
the relaunch binds it. A failure is held until the page has read it (at
most five seconds), so a failed upgrade no longer reloads silently onto
the old version.

The page polls that status, measures the bar while bytes are counted
and slides it otherwise, and says when the new Raven is starting. Its
twenty-minute ceiling now counts silence rather than the whole upgrade,
so a slow download that is visibly moving is no longer given up on.

The quick-start's update section now covers updating from the page.

Co-authored-by: Claude (claude-opus-5-5) <noreply@anthropic.com>
While a turn runs, the composer's placeholder said a message typed now
waits for the turn to end. That stopped being true when mid-turn sends
started merging into the running turn at its next gap; the queue is only
the fallback for an old gateway or a dropped socket. It now says Raven
receives the new instruction directly.

Co-authored-by: Claude (claude-opus-5-5) <noreply@anthropic.com>
… one

The first redraw of the live download line said "0 KB/s", before a
second sample existed to measure a rate from, which reads as a stall.
The page already leaves the rate out until it is known; the terminal
now does the same.

Co-authored-by: Claude (claude-opus-5-5) <noreply@anthropic.com>
The shutdown test only started reading after the server had finished
stopping, so under load it never answered the server's close frame and
saw the transport drop (1006) instead of the close (1001). A page is
always reading. Reading concurrently made it pass 25 of 25 runs under
CPU load, and it still fails without the on_shutdown hook.

Co-authored-by: Claude (claude-opus-5-5) <noreply@anthropic.com>
Every entry-point test stubs `_can_open_a_browser`, so its own logic
never ran; it now has a test of both answers. `close_all` gets one for a
socket that raises on close: the others still close, and the broken one
is forgotten. The `_shutdown` closure's pragma moves to its def line,
where it excludes the function rather than only its body.

Co-authored-by: Claude (claude-opus-5-5) <noreply@anthropic.com>
The helper is started as `python -I -c <bootstrap>`, with the whole
source base64-encoded into the command line, and Windows caps a command
line at 32,767 characters. main was already at about 25,100; the
download and status code in this branch took it to 39,730, and the
Windows self-upgrade job failed with WinError 206 before the helper
ran. The source is now zlib-compressed before encoding (12,953
characters). A test holds the bootstrap under 20,000 on every OS, since
otherwise only a Windows machine notices.

Co-authored-by: Claude (claude-opus-5-5) <noreply@anthropic.com>

@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.

Blocking: a stalled helper can leave the page trapped in the upgrade shade indefinitely.

I reviewed the PR-specific diff against GitHub's actual base (2f5027cdf), plus the requested local github/main...HEAD diff, the upgrade/supervisor callers and history, backward compatibility, test changes, and the repository rules in AGENTS.md, CONTEXT-MAP.md, CONTEXT.md, and ui-web/CONTEXT.md. I found one blocking defect inline; I refuted the other candidates around supervisor teardown, port ownership, socket shutdown, busy-work detection, and the bare-command fallback.

Verification: uv run pytest tests/test_cli_gateway_commands.py tests/test_cli_tui_commands.py tests/test_rpc_serve_control.py tests/test_rpc_system.py tests/test_rpc_transport.py tests/test_updates_upgrade.py -x passed 380 tests after uv sync --extra dev; focused web tests passed 49 tests; the full web suite passed 3,096 tests; TypeScript type-check and RPC generation check passed; ESLint completed with four pre-existing warnings and no errors; source-language and large-file gates passed. The initial Python attempt failed in fixture setup because the default environment lacked raven_everos; no test assertion ran in that attempt.

Comment thread ui-web/src/app/updates.ts Resolved

@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.

Blocking: the unchanged progress watcher still treats status-server liveness as progress and can wait forever.

The new revision only compresses the upgrade-helper bootstrap so it fits the Windows command-line limit. I reviewed that delta, its caller and tests, backward compatibility, and the applicable repository rules; the zlib/base64 bootstrap preserves the helper source and leaves substantial room under the platform limit. I found no new issue in this commit. The existing inline blocker remains open because ui-web/src/app/updates.ts is unchanged.

Verification for this revision: uv run pytest tests/test_updates_upgrade.py -x passed 156 tests. The generated bootstrap is 12,953 characters, and a representative fully quoted Windows launch command is 13,323 characters.

Every status answer refreshed the page's twenty-minute ceiling, even when
the phase and byte count had not changed. The helper's status server is
its own thread and keeps answering while a download or uv is stuck, so a
stalled helper extended the deadline forever, and the running card has
no close action while a reload resumes it from local storage: the reader
was trapped. Only a new phase or more bytes now move the deadline, so a
slow download that keeps moving is still waited out and a stuck one gives
up with the manual command after twenty minutes without progress.

Reported in review by gloryfromca.

Co-authored-by: Claude (claude-opus-5-5) <noreply@anthropic.com>

@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.

I reviewed the fix delta and re-checked the affected watcher, helper behavior, history, backward compatibility, test changes, architecture boundary, and applicable AGENTS.md/CONTEXT rules. The prior blocker is fixed and its thread is resolved; I found no new plain error.

Verification: npm test -- src/app/updates.test.ts passed 19 tests, and npm run type-check passed.

@0xKT
0xKT merged commit b27126e into main Sep 26, 2026
42 of 43 checks passed
@0xKT
0xKT deleted the feat/page_upgrade_and_entry branch September 26, 2026 16:36
@0xKT 0xKT mentioned this pull request Sep 27, 2026
4 of 12 tasks
0xKT added a commit that referenced this pull request Sep 27, 2026
## Summary

Bump the package version from 0.2.2 to 0.2.3 for the next release. Only
pyproject.toml and uv.lock change.

Since v0.2.2, main merged 13 PRs: three features (#802 an experimental
Curator that cultivates harness-of-harnesses workers, #805 one header
per desk pane, #806 upgrade from the page with progress), four fixes
(#792 and #799 name grok and copilot connection failures, #807 records
the questions an unattended turn could not ask, #808 keeps the report
shape on interrupted research turns) and six docs PRs. None carries a
BREAKING CHANGE footer, so this is a patch bump, the way v0.2.1 and
v0.2.2 were for their features. The tag and release notes follow once
this lands.

## Type

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

## Verification

- `uv lock` on a clean worktree cut from origin/main: Resolved 251
packages, Updated raven v0.2.2 -> v0.2.3
- `git diff --stat origin/main..HEAD`: pyproject.toml and uv.lock, 2
insertions, 2 deletions
- `PYTHONPATH=. python scripts/check_commit_messages.py
origin/main..HEAD`: exit 0
- `npx commitlint --from origin/main --to HEAD --config
commitlint.config.cjs`: exit 0
- `uv version` in the worktree: raven 0.2.3
- No test run: the change is version metadata only.

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

## Risk

No code change. Rollback is reverting this commit before any v0.2.3 tag
is pushed.

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

## Related Issues

N/A

Co-authored-by: Claude (claude-fable-5-1) <noreply@anthropic.com>
LivXue pushed a commit that referenced this pull request Oct 12, 2026
…806)

## Summary

The upgrade button on the page could never work for a normal install,
and when an upgrade did run, neither the page nor (on a slow link) the
terminal showed anything that moved. This PR makes the page upgrade
work, shows its progress, and fixes what an end-to-end run of it turned
up, plus two entry-point changes from the same pass.

**The page upgrade works on `raven web`.** `raven web` runs `raven
gateway`, so nearly every page is gateway-hosted, and `system.upgrade`
refused all of them (`gateway_hosted`): its relaunch brought back a bare
`raven serve`, which builds no ChannelManager, so the IM channels would
have vanished. Now:

- On every page mount the gateway hands the page its own graceful stop
(the path `gateway.shutdown` takes), its busy check, and its supervisor
pid. Every mount, because a config swap tears the page down and disarms
it.
- On upgrade the gateway exits 0. The `raven web` supervisor already
reads a zero exit as "stand down" (its docstring names
`system.upgrade`), so it exits too.
- The helper waits for the **supervisor**, not the gateway, then
installs and relaunches `raven web --supervise` on the same port with
the same token and cookie. Waiting on the supervisor matters: its
cleanup removes `web.json` after the gateway exits, and a supervisor
started before that would lose its state file, inviting the next `raven
web` to start a second one.
- Refused, each with its own reason, when nothing would bring the
gateway back (started by hand, or `raven web --foreground`), when work
the page cannot see is running (an IM turn, a sub-agent, a pending
question), or when there is no executable to relaunch from. The config
swap and the upgrade now share one busy check.

This takes effect for upgrades that start from a release containing it;
a gateway on an older release still refuses with the old message.

**The page shows the upgrade's progress.** The card said "Upgrading
Raven..." and nothing else until the new Raven answered, because the
process that served the page is gone for the whole install.

- The helper downloads the release itself, where the bytes can be
counted, and hands uv the local files so uv only resolves dependencies.
All or nothing: if any asset cannot be fetched, uv gets the original
URLs and downloads them as before. Files live under
`~/.raven/cache/upgrade/<version>`; earlier versions' are cleared.
- Once the old Raven has exited, the helper answers on the page's port
(recorded in the upgrade marker): `/upgrade/status` with phase, bytes
done and total, and rate; 503 to everything else, so the page's "is it
back?" probe keeps waiting and a reload gets a self-refreshing wait
page. It answers only the page's own Host, and lets the port go before
the relaunch binds it.
- A failure is held until the page has read it (at most five seconds).
Before, a failed install relaunched the old version and the page
reloaded onto it silently.
- The page polls the status, measures the bar while bytes are counted
and slides it otherwise, and says when the new Raven is starting. Its
twenty-minute ceiling now counts silence, not the whole upgrade, so a
slow download that is visibly moving is not given up on.

**Two bugs the end-to-end run exposed**, both fixed here:

- *A red "the service is not running, start it with raven web" bar
during the upgrade.* The transport's health probe counted any answer but
401/403 as "the gateway is back and refused this session", so the
helper's 503 made it give up. A 5xx now means "not back yet". Following
that bar's advice mid-upgrade is exactly what would start a second
supervisor.
- *The old gateway took 46 seconds to exit, blocked on the page's own
open WebSocket.* aiohttp's cleanup waits for live handlers instead of
closing websockets. The WS gateway now closes its sockets from
`on_shutdown`; the same run dropped to 4 ms. This also speeds up `raven
web --stop` with a page open.

**`raven gateway` refuses a half-written install.** `raven serve` has
guarded this since the upgrade marker existed, but the gateway never
did, and the gateway is what `raven web` supervises. A restart inside an
upgrade window came up on the half-written tree and, because the page
route is chosen once, served the placeholder page until restarted by
hand. The guard runs before the instance lock. It is also a precondition
for the restart above.

**Refusals are localized.** The page pasted the server's English
`detail` into an otherwise Chinese card. Refusals it can name come from
the catalogue; refusals whose detail carries facts from this run keep
them. A server-side `busy` gets the same wait notice as the page's own
busy turn, not the "run this in a terminal" card.

**The terminal upgrade names its download size.** uv draws one bar per
package and no total, so a large release reads like a stalled install (a
real report: a 72 MiB upgrade at about 50 KB/s looked frozen). The
helper prints each asset's size and the total before downloading, from
HEAD requests bounded at 5 seconds, headline first so a hanging probe
adds no silence; the terminal then shows the helper's own live progress
line. Output is flushed so it keeps its place in `web.log`.

**Bare `raven` opens the page.** The installer ends in `raven web`, but
the command typed the next day landed in the TUI. Bare `raven` now does
what `raven web` does; where no browser can be opened it runs the TUI
instead, decided before anything starts. `raven web` and `raven tui` are
unaffected.

**Composer placeholders.** Empty: "Describe your task, or have Raven
build an agent for it" becomes "Describe your task". While a turn runs:
"it is sent once this turn ends" was stale (mid-turn sends merge into
the running turn; the queue is only a fallback), so it now says Raven
receives the new instruction directly.

## Type

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

## Verification

End to end, three times, in a sandboxed uv tool install (own
`UV_TOOL_DIR` and `RAVEN_HOME`, no plugins, so nothing reached a real
memory server), upgrading 0.2.90 to 0.2.91 from a local stand-in for the
GitHub release API, clicking the real rail-foot upgrade row in a
browser:

- First run (fast local download): page back after 17 seconds, reloaded
by itself, still signed in; served by `python -P -m raven gateway` (not
`serve`) on the same port; `web.json` names the new supervisor, which is
the new gateway's parent; old supervisor and gateway gone.
- Second run, with the release trickled at ~200 KB/s: the progress
showed, and the run found the two bugs above (the red bar, and the old
gateway taking 46 seconds to exit).
- Third run, same trickle, after the fixes: the card read "Downloading
the new version: 0.6 / 6.1 MiB, 191 KB/s" five seconds after the click,
then 2.4 / 6.1 MiB with the bar measuring; no red bar; the old gateway's
page socket closed 4 ms after the control plane stopped; uv prepared the
package from the local file in 157 ms; the page reloaded itself onto
0.2.91.
- `raven upgrade` in a real pseudo-terminal against the same trickle:
the live line redrew in place 98 times from 0.1 to 6.1 of 6.1 MiB, then
uv installed from the local file.

Commands, all on this branch rebased onto current main:

- `make coverage` - 26,955 passed. 21 failed, and they are the same 21
that fail on unmodified main on this machine (research-tool and
simulation tests, and one that assumes LibreOffice is absent); a 22nd
was a load-sensitive test added here, fixed in this PR and then 25 of 25
under CPU load
- `scripts/coverage_gate.py diff --base-ref github/main --threshold 90`
- 100% (77 of 77 changed lines)
- `npm test --prefix ui-web` - 3096 tests pass
- `make lint-python` - pass; `uv run lint-imports` - 10 contracts kept,
0 broken
- `make lint-ui` - 0 errors (4 pre-existing `exhaustive-deps` warnings
in files this PR does not touch); `npm run lint:i18n --prefix ui-tui` -
up to date
- `node ui-web/scripts/check-page.mjs`, `check-css.mjs`,
`check-class-namespace.mjs` after `npm run build` and `build.py` - OK
- `mkdocs build --strict` - pass
- `npx commitlint --from github/main --to HEAD`,
`scripts/check_commit_messages.py`, `make check-source-language` and
`make check-large-files` with `COMMIT_RANGE=github/main..HEAD` - pass

New tests were mutation-checked; each edit below turns the test written
for it red. Server: relaunching `serve` instead of the supervisor,
waiting on the gateway instead of the supervisor, dropping the
unsupervised refusal, the busy refusal, the no-relaunch guard, or the
page-mount hand-over; not closing sockets on shutdown. Helper: never
downloading, not closing the status server before the relaunch, dropping
the Host check, not holding a failure, dropping the manifest or its
flush. Page: counting the ceiling from the start instead of from
silence, ignoring a held failure, dropping the "starting" line, a bar
that ignores or overshoots the fraction, the old 5xx probe rule, server
sentence instead of catalogue, `busy` routed to the failure card. Entry
point: never opening the page, opening it on the wrong port.

Note for reviewers running the gates locally: `make check-commits`
hardcodes `--from origin/main`, and `check-source-language` /
`check-large-files` default `COMMIT_RANGE` to `origin/main..HEAD`. Where
`origin` is not GitHub those ranges fail on unrelated commits; pass the
GitHub base explicitly.

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

The quick-start's update section (English and Chinese) now covers
updating from the page.

## Risk

User-visible changes:

- Bare `raven` opens the browser page instead of the TUI, leaving the
resident gateway `raven web` always leaves. Headless machines keep the
TUI; `raven tui` still names it outright.
- The page upgrade restarts the gateway, briefly disconnecting IM
channels. It is refused while any turn, sub-agent or pending question is
running.
- During a page-started upgrade the page's port is answered by the
upgrade helper (status and 503s) instead of nothing.
- The helper keeps the current version's wheels under
`~/.raven/cache/upgrade/<version>` (about 75 MB for a release with its
engines), because the tool's receipt names them as the install source.
- A `raven gateway` that starts inside an upgrade window waits it out
and exits instead of serving; the supervisor restarts it once the
install is whole.

Known edge: running `raven web` by hand in the seconds between the old
supervisor exiting and the new one starting can start a second
supervisor beside the relaunched one. The install itself stays protected
by the upgrade marker, and the page no longer tells the reader to do
this.

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

Security: the status endpoint is unauthenticated but loopback-only,
answers only the page's own Host (so a rebound host name gets 421), and
exposes version numbers and byte counts; the relaunch carries the same
token and cookie the `raven serve` path already did. Rollback: revert
the squash commit. Upgrades started from a release without this change
keep the old refusal, so there is no mixed-version state to unwind.

## Related Issues

N/A

---------

Co-authored-by: arelchan <204152633+arelchan@users.noreply.github.com>
Co-authored-by: Claude (claude-opus-5-5) <noreply@anthropic.com>
LivXue pushed a commit that referenced this pull request Oct 12, 2026
## Summary

Bump the package version from 0.2.2 to 0.2.3 for the next release. Only
pyproject.toml and uv.lock change.

Since v0.2.2, main merged 13 PRs: three features (#802 an experimental
Curator that cultivates harness-of-harnesses workers, #805 one header
per desk pane, #806 upgrade from the page with progress), four fixes
(#792 and #799 name grok and copilot connection failures, #807 records
the questions an unattended turn could not ask, #808 keeps the report
shape on interrupted research turns) and six docs PRs. None carries a
BREAKING CHANGE footer, so this is a patch bump, the way v0.2.1 and
v0.2.2 were for their features. The tag and release notes follow once
this lands.

## Type

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

## Verification

- `uv lock` on a clean worktree cut from origin/main: Resolved 251
packages, Updated raven v0.2.2 -> v0.2.3
- `git diff --stat origin/main..HEAD`: pyproject.toml and uv.lock, 2
insertions, 2 deletions
- `PYTHONPATH=. python scripts/check_commit_messages.py
origin/main..HEAD`: exit 0
- `npx commitlint --from origin/main --to HEAD --config
commitlint.config.cjs`: exit 0
- `uv version` in the worktree: raven 0.2.3
- No test run: the change is version metadata only.

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

## Risk

No code change. Rollback is reverting this commit before any v0.2.3 tag
is pushed.

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

## Related Issues

N/A

Co-authored-by: Claude (claude-fable-5-1) <noreply@anthropic.com>
LivXue added a commit that referenced this pull request Oct 23, 2026
…s recovery (#1003)

## Summary

- The A remainder of #716 after #806 landed most of A and B: a source
checkout is no longer offered a page upgrade it can never take, and
every refusal that has a recovery of its own names it instead of the
card's fixed `raven upgrade`.
- `update_notice._upgrade_command_works()` now excludes editable
installs, as its docstring always claimed: an editable uv-tool install
(a checkout under ./install.sh) passes the receipt check alone, and both
`raven upgrade` and the page's fallback refuse it.
- `system.upgrade` refusals carry an optional `data.command` the card
copies: `unsupervised` names `raven upgrade` then `raven web`, and a new
`editable` refusal fires before the release lookup and names `git pull`
plus the platform's install script (`.\install.ps1` on Windows,
`./install.sh` elsewhere).
- The refusals classify before they manage: not_serving, then editable,
then the gateway's unsupervised and busy answers. An editable checkout
under a hand-started gateway therefore gets the checkout's own recovery,
not the unsupervised sequence whose first command (`raven upgrade`)
always refuses a checkout.
- The failure card renders and copies the refusal's own command, line
per line, keeping the default when none was given; the `editable` frame
is translated (`gui.upg.why.editable`, en and zh, TUI catalogue
regenerated).
- Tests that simulate a release install now pin `_is_editable_install`
to False: a dev venv (and CI) install raven editable from the tree, so
without the pin the new pre-check would fire everywhere.

## Type

- [x] Fix

## Verification

- [x] Relevant tests pass locally: `uv run pytest
tests/test_rpc_system.py tests/test_updates_update_notice.py` -> 84
passed (the overlap test arms the hosted-unsupervised state with the
editable probe true and asserts the checkout answer wins); `npx vitest
run src/state/upgradeShade.test.ts src/app/updates.test.ts` -> 25 passed
(both file orders); `npm run type-check` and eslint on the four touched
web files clean
- [x] The new coverage fails on the base: stashing only the source
changes -> the editable-refusal test, the card-command test and the
overlap test fail; restoring -> pass
- [x] Relevant lint / type checks pass locally: `ruff check` + `ruff
format --check` on the touched Python files; `ty check` identical to a
stashed baseline (zero new)
- [ ] User-facing docs or screenshots are updated when needed: the
refusal copy and its zh translation are the user-facing surface and are
in the catalogue

## Risk

- [x] Security impact considered: the command strings are constants
chosen by reason, never assembled from user input
- [x] Backward compatibility considered: refusals without a `command`
keep today's card verbatim; the button's absence on checkouts is the
docstring's promised behaviour
- [x] Rollback path is clear: plain revert

## Related Issues

Part of #716 (the remainder scoped in its thread; #806 already landed
most of A and B)

---------

Co-authored-by: Dizhan Xue <xuedizhan17@mails.ucas.ac.cn>
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