Repository navigation
feat: let a release require its update - #1070
Merged
Merged
Conversation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
A release can require its update. A
> [!CAUTION]block under a release'sCHANGELOG.mdheading, the fourth GitHub alert kind beside IMPORTANT, WARNING and NOTE, marks the update as required. A lich behind such a release shows an update prompt with no Later and opens no new agent session until it is installed.Both frames come from a rig: this checkout's backend built as 0.86.1 with its releases URL overlaid onto a local list whose 0.87.0 carries the block, and this checkout's window on a private display.
How
Signal.
patchnotes.RequiresUpdatereads a caution block out of release notes with the parser the What's new dialog already uses.release.ymlneeded no change: itsawkcopies everything under the version heading, so the block reaches the release body (run here on a section with a caution block, and the published v0.81.0 body carries its IMPORTANT block the same way). The dialog draws a caution callout in the Destructive token with its own glyph.Detection.
appupdatenow reads the releases list instead ofreleases/latest: still one request an hour, answering both which release is the latest (the highest version in the list) and whether any release newer than the running build is required.ghrelease.Releasescarries the bodies andReleaseTagsis built on it;LatestTaghad no caller left and is gone. A page of 100 releases measured 2.2 MiB decoded, past the 1 MiB metadata cap, so the list has its own cap,ListLimit.AppUpdateStatusgainsRequired; the contract golden moves on purpose.Enforcement.
SessionRefused, so the window,lich open, the MCP tools, a handoff, a fork and a subagent card are all answered in one place.internal/spawnasks the same question before it creates a worktree, so a refused open leaves nothing behind. The window does not go throughspawn.Open, which is why the gate sits at the insert.appupdate.Serviceremembers the last status, whoever asked for it, andRequiredUpdateanswers from it for two hours before looking again itself, so a lich with no window still learns of the release and one with a window spends no extra request. A build that is not a release looks nothing up.LICH_PORTandLICH_TOKENonly a lich session exports, so refusing shells would have broken the update's own path on every package-manager install.lich.appUpdateDismissed, comes back when swiped away, and every hourly check raises it again once its action closed it. It keeps the ordinary action: the installer button, the install command, or the release page.Tests
Tests whose fixtures changed, and why: the
appupdatefixtures (TestStatus, the Apply servers, the Windows installer end to end overlay) now serve a releases list, because the endpoint polled changed. The fiveLatestTag/parseTagtests went with the function; their failure cases (unreachable server, read past the cap, unbuildable URL, malformed body, non-200) are asserted onReleasesandReleaseTags.New:
patchnotes: a caution block under the release banner, with CRLF, beside other kinds, absent, quoted inside a group, with no text.ghrelease: bodies carried, drafts and pre-releases left out, a list larger than the metadata cap read, the list cap enforced.appupdate:Requiredwhen the latest or a middle release carries the block; false when none does, when the required release is the one running or behind, on a dev build, on a build between tags, and when the lookup fails; the refusal names both versions, the install command and the release URL; the gate answers from the last lookup without another request, looks again once stale, and looks nothing up on a dev build.store: every insert entry point refuses an agent session and leaves the project untouched; a shell opens; a parked session comes back; nothing is refused once the gate lifts.spawn: Open, Open on a new worktree, Open from outside a session, OpenSubagent and Handoff are refused with the gate's own error and leave no worktree, row, terminal or card; a shell opens.Run against the rig backend through the command line:
lich open --kind claudeandlich open --kind codex --worktree ...exit 1 with the refusal and create no worktree, andlich open --kind shellopens.Local gate, each step alone in the memory jail:
gofmt -l .clean,go vet ./...clean,go test ./...green (47 packages), coverage 89.9% by the measure CI gates on.GOOS=windows go vet -tags installer_e2e ./internal/appupdate/clean, for the Windows-only test file this touches.native/:gofmt -l .clean,go vet ./...clean,go test ./...green.Docs
docs/ceilings.md(the signal and who understands it, no GitHub no gate, what is refused and what is not, a package-manager install still needs the command run), the Releases checklist inCLAUDE.md, thelich-changelog-i18nskill,SECURITY.mdandCHANGELOG.md. The two new strings are in every locale.