Repository navigation
feat: server-advertised updates from a configurable release repository - #19
Conversation
T3CODE_RELEASE_REPOSITORY (owner/name, default pingdotgg/t3code) now selects whose GitHub Releases the release index, `t3 update`, the boot service, remote self-update and the install scripts use. Downloads follow the repository unless T3CODE_RELEASE_BASE_URL names a mirror, so a fork can publish its own archives without a mirror. The release index picks the highest version on a channel by semver precedence instead of the first match in publish order, so a later patch for an older line no longer wins. The install scripts classify tags with the same rule the runtime uses: only nightly and preview prereleases form their own trains, every other version is stable. Asking for a version whose SHA256SUMS is missing now fails with "t3@<version> is not published in <repository> releases." (or the mirror) from both `t3 update` and server.updateServer, instead of a generic "Could not prepare" error. The release-index lookup moves from the update CLI into apps/server/src/cloud/releaseIndex.ts so the server can reuse it. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A server run by the boot-service launcher never showed an update in the web app it serves, because the notice only fired when the client was newer than the server, and the target it sent was always the client's version, which may not exist on the release train the server installs from. The server now checks its own channel in its release repository at startup and every six hours (backing off on failure; never when unmanaged, on preview builds, or with T3CODE_UPDATE_CHECK_ENABLED=false) and advertises a strictly newer release as the optional `availableServerUpdate` descriptor field. Clients offer that version through the existing update notice, Settings -> Connections row and bulk update, with the same progress UI and thread continuation. When both apply, the server's advertised release wins over client-version skew. Dismissals are keyed by the offered target, so dismissing one release does not hide the next; the key is unchanged for client skew. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The fork CI rejected every overlay change under apps/web, apps/mobile, apps/desktop and packages. The in-app update from the configured release channel (decided by the Organization Admin on 2026-09-25) has to change the web update notice, the environment descriptor contract and the shared release index, so the guard now allows exactly those files, each group with its reason. It stays fail-closed: any other path under those roots fails and is listed in the error. The release contract test asserts the guard still filters those roots, that the allowlist opens with a reason, and that every entry is an exact existing file under a guarded root (no globs, directories or duplicates). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
agentrozjedemeai
left a comment
There was a problem hiding this comment.
Review of exact head 3b8ca84 (base 7b61df2): changes requested.
-
[P1] apps/server/src/cloud/releaseIndex.ts:76-77 — The resolver returns the first channel hit on page 1 instead of the highest SemVer over the searched pages. With 0.0.42-lazurio.1 and 99 nightlies on page 1, and 0.0.42-lazurio.2 on page 2, it returns .1 and never fetches page 2; a server already on .1 advertises nothing. Accumulate the best candidate over the bounded pages (or prove an ordering-based early stop) and test this case.
-
[P2] packages/shared/src/cliRelease.ts:136-138, apps/server/src/cloud/releaseIndex.ts:104 — The tag regex accepts non-exact SemVer such as v0.0.43-lazurio.01; the server advertises it as newer than 0.0.42-lazurio.2, but isExactServiceVersion rejects it at the self-update/launcher boundary. Exclude invalid tags before choosing a candidate and add a regression for an invalid newest tag alongside a valid release.
-
[P2] scripts/install.sh:86-90 and scripts/install.ps1:53-56 — The nightly/preview suffix regex is not anchored immediately after major.minor.patch. Both installers classify v1.2.3-acme-nightly.20260901.1 as nightly, while cliReleaseChannelOf classifies it as stable. Align both installer filters with the runtime channel rule and test the cross-surface case.
-
[P2] apps/server/src/cloud/releaseIndex.ts:119-123 — Config.boolean is parsed before checking managed/preview status. An inherited malformed T3CODE_UPDATE_CHECK_ENABLED value can fail construction of an unmanaged server that should not perform any check. Apply the eligibility gate before reading this flag (or otherwise avoid making an irrelevant setting fatal).
Focused existing checks passed (64 server/shared/web tests, 19 UI/contract tests, and 7 release-contract tests); two extra disposable regression tests for findings 1 and 2 failed on this head with the actual wrong values. The update action remains click-triggered and the archive remains SHA256-verified. The unchanged client-skew fallback is deliberate per the stated scope, so I am not counting that existing behavior as a new defect.
Review of 3b8ca84 found four gaps, each now covered by a regression test: - The release resolver returned the best release of the first page with a channel hit. The index is ordered by publish time, so a higher release on a later page was missed. It now reads every page up to the last one (at most 10 requests) and compares versions across all of them. - Tags that are not exact SemVer, such as v0.0.43-acme.01, could be chosen and advertised although the launcher refuses them. They are dropped with the launcher's own isExactServiceVersion before choosing. - install.sh and install.ps1 matched a nightly or preview suffix anywhere in the tag, so v1.2.3-acme-nightly.20260901.1 was nightly to the installers but stable to the runtime. Both now use the runtime rule (the train identifier directly after major.minor.patch), also for the preview warning. scripts/install-scripts.test.ts runs install.sh against a fake curl and checks install.ps1's rule against cliReleaseChannelOf. - T3CODE_UPDATE_CHECK_ENABLED was parsed before the managed/preview gate, so a malformed value could fail an unmanaged server that never checks. The gate now runs first. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
agentrozjedemeai
left a comment
There was a problem hiding this comment.
Re-review of exact head a5a1725 (base 7b61df2): the four blocking findings from review 5323299236 are addressed. The resolver scans bounded pages and chooses the highest valid exact SemVer; the install.sh and install.ps1 train filters match the runtime (including fork-suffixed stable tags); and unmanaged/preview servers skip malformed update-check configuration. Focused server/shared/install/release-contract tests passed (10+21+4+7), scoped typechecks and formatting passed; the real PowerShell installer selected the expected stable/nightly/preview tags with mocked downloads. CI is green on this head and all four conversations are resolved. The unchanged upstream client-version-skew fallback is not a new blocker. No real-machine release/update smoke was performed.
The update-channel change (PR #19) intentionally edits listed upstream client and shared files. The runbook now names that overlay item and tells the release owner to re-check the allowed_upstream_changes list on every upstream rebase. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The update-channel change (PR #19) intentionally edits listed upstream client and shared files. The runbook now names that overlay item and tells the release owner to re-check the allowed_upstream_changes list on every upstream rebase. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Proč
Operátor, který používá web verzi T3 Code servírovanou headless serverem (Linux VM nebo Mac Mašina), dnes nikdy neuvidí nabídku aktualizace serveru. Banner „Server update available“ se ukáže jen tehdy, když je klient novější než server, a prohlížeč servírovaný tím samým serverem má vždy stejnou verzi. Když už se banner objeví (třeba z upstream desktop klienta), cílová verze je verze klienta, kterou náš kanál nemusí vůbec mít. Upstream cesta „zkopíruj
npx t3@<verze>a spusť ji v terminálu“ je pro operátora nešikovná.Matěj 2026-09-25 rozhodl: operátor má vidět standardní banner s tlačítkem Update, když na nakonfigurovaném kanálu vyjde novější release, a kliknutí spustí už existující upstream self-update (launcher stáhne
t3-<ver>-<platform>.tar.gz+SHA256SUMS, ověří, vyzkouší a přepne s rollbackem).Co PR zavede
První dva commity jsou napsané obecně (bez Lazurio názvů v kódu), aby šly nabídnout upstreamu beze změny:
feat(server): install and update from a configurable release repositoryT3CODE_RELEASE_REPOSITORY(owner/name, výchozípingdotgg/t3code). Používá ji index vydání,t3 update, boot service (t3 service install), vzdálený self-update iinstall.sh/install.ps1. Stahování jde z GitHub Releases tohoto repa, pokudT3CODE_RELEASE_BASE_URLneurčí mirror.0.0.42-lazurio.10>.9), ne první v pořadí publikace. Čte všechny stránky indexu až po poslední (max. 10 požadavků) a porovnává verze napříč nimi. Drafty přeskakuje a tagy, které nejsou přesný SemVer (např.v0.0.43-lazurio.01), zahazuje stejným pravidlem jako launcher (isExactServiceVersion).cliReleaseChannelOf): vlastní vlak mají jennightly/previewhned za major.minor.patch, všechno ostatní (i-lazurio.Nnebo-acme-nightly.…) je stable; stejné pravidlo platí i pro varování o preview. Bez toho byinstall.shz našeho repa nenašel žádný stable release.SHA256SUMS), server i CLI odpoví jasně:t3@X is not published in Lazurio/t3code releases.(případně název mirroru) místo obecného „Could not prepare t3@X“.t3 updateCLI do sdílenéhoapps/server/src/cloud/releaseIndex.ts.feat: offer a managed server's own newer release as its update targetserverSelfUpdate: boot-service) kontroluje při startu a pak každých 6 hodin nejnovější release na svém vlastním kanálu v nakonfigurovaném repu. Při chybě zkouší znovu s exponenciálním backoffem (1 min → max 6 h) a drží poslední odpověď.T3CODE_UPDATE_CHECK_ENABLED=false. Proměnnou čte až po této kontrole, takže chybná hodnota neshodí server, který kontrolovat nemá.availableServerUpdate: { version }, jen když je verze striktně vyšší nežserverVersion(stejné SemVer pravidlo, jaké vynucuje launcher). Schéma je zpětně kompatibilní (optionalKey, starší klienti pole ignorují).targetVersion. Stejné komponenty, stejný progress UI, stejná volba pokračování vláken. Funguje i v prohlížeči servírovaném tím samým serverem. Pokud platí zároveň client-skew, vyhrává verze od serveru (server ví, z jakého kanálu umí instalovat; upstream desktop klient novější než náš kanál tak nevyrobí cíl, který skončí 404). Jinak se client-skew logika nemění..2neschová.3. Pro client-skew je klíč identický jako dřív.Záměrně se nemění: launcher, protokol self-updatu, preflight, rollback,
server.updateServerkontrakt, mobilní aplikace (nemá UI pro update serveru), desktop-managed servery, chování klienta pro servery bez nového pole. Nepřidává se nový stream event: nová verze se klientovi dostane v dalším snapshotu konfigurace (připojení, reconnect, reload stránky), ne okamžitě do již otevřeného tabu.Rollout pro Lazurio
T3CODE_RELEASE_REPOSITORY=Lazurio/t3code(unit zt3 service installsám nastavuje jenT3CODE_HOME; launcher předává prostředí dítěti). To je práce pro Machines.vX.Y.Z-lazurio.Ns archivy aSHA256SUMS(paralelní PR release: publish CLI archives as the Lazurio update channel #18). Nepoužívat GitHub draft, index drafty přeskakuje.0.0.42-lazurio.1<0.0.42. Server hlásící0.0.42proto první přechod na náš kanál nenabídne (a launcher by ho stejně odmítl); první převod přes pin v Machines nebot3 update 0.0.42-lazurio.1 --allow-downgrade.Fork CI guard (třetí commit)
Fork CI (
lazurio-fork-ci.yml) dosud odmítal jakýkoli overlay podapps/(web|mobile|desktop)apackages/. Tahle funkce z principu mění web banner, kontrakt deskriptoru a sdílený index vydání, proto na rozhodnutí delegujícího agenta (Matějovo rozhodnutí o tlačítku Update má přednost před starým pravidlem) commitci: allow the reviewed in-app update files in the fork overlay guard:apps/web/src/components/ChatView.tsx,apps/web/src/components/chat/useAutoBalanceUpdateBanner.tsx,apps/web/src/components/settings/ConnectionsSettings.tsx,apps/web/src/versionSkew.test.ts,apps/web/src/versionSkew.tspackages/contracts/src/environment.tspackages/shared/src/cliRelease.test.ts,packages/shared/src/cliRelease.tsscripts/lazurio-release-contract.test.mjsmá nový samostatný test (existující testy nemění, aby rebase s PR release: publish CLI archives as the Lazurio update channel #18 byl triviální): guard dál filtruje ty kořeny a failuje, allowlist začíná důvodem a každá položka je přesný existující soubor pod hlídaným kořenem, bez globů, adresářů a duplicit.packages/shared/src/semver.tsspadne a soubor vypíše.docs/operations/lazurio-fork-release.md(tabulka overlay commitů a věta „Nothing in the overlay touches clients, shared packages, or wire contracts“) aktualizuje přepis runbooku v PR #18; tady se záměrně nemění, aby nevznikl konflikt.Review (Pablo, 3b8ca84) a opravy
Čtvrtý commit
fix: pick installable releases across the whole index and one train ruleřeší všechny čtyři nálezy, každý s regresním testem, který na předchozím HEADu padá:.1+ 99 nightlies na stránce 1,.2na stránce 2 →.2.v0.0.43-lazurio.01) → zahozeny přesisExactServiceVersionpřed výběrem. Test: neplatný nejnovější tag vedle platného.2.scripts/install-scripts.test.tsspouští skutečnýinstall.shproti falešnémucurla ověřuje pravidloinstall.ps1proticliReleaseChannelOf, včetněv1.2.3-acme-nightly.20260901.1(stable všude).T3CODE_UPDATE_CHECK_ENABLEDse četl před kontrolou způsobilosti → pořadí prohozeno. Test:maybeu unmanaged serveru i preview buildu.Greptile navíc hlásil „Fork updates become unavailable“ (versionSkew fallback na verzi klienta). Záměrně beze změny: fallback je upstream chování a server teď neznámou verzi jasně odmítne (
not published in <repo> releases); nový signál kvůli nálezu bota nepřidáváme. Vlákno je zodpovězené a vyřešené, stejně jako ostatní tři.Ověření
Lokálně na HEAD (Node 26 / pnpm 11.10.0,
pnpm install --frozen-lockfile):vp fmt --check– OKvp run --filter t3 typecheck,vp run --filter @t3tools/web typecheck,packages/shared,packages/contracts,scripts– OKvp lint --report-unused-disable-directives– 0 chyb;knip --exportspro server/shared/web/contracts – čistéapps/server:vp test run src/cli/config.test.ts src/auth src/environment/ServerEnvironmentLabel.test.ts src/environment/ServerEnvironment.test.ts src/cloud src/cli/update.test.ts src/cli/service.test.ts– 28 souborů, 280 testů OK;src/server.test.ts– 189 OKpackages/sharedcliRelease.test.ts+semver.test.ts– 21 OK;packages/contractsenvironment.test.ts– 5 OKapps/webversionSkew.test.ts+ServerUpdateAction.test.tsx– 35 OKscriptsinstall-scripts.test.ts– 4 OK (install.sh reálně; install.ps1 jen jeho regex, pwsh není k dispozici)node --test scripts/lazurio-release-contract.test.mjs– 7/7 OKNeověřeno end-to-end na skutečné Mašině (chybí vydání na kanálu a env ve službě).
Rizika
-rc.1by byly stable); srovnává je s tím, co už dnes dělát3 update.Upstream proposal
Headless servers offer their own updates, from a configurable release repository.
Today a browser served by
t3 servenever sees a server update: the notice only fires when the client is newer than the server, and the target it installs is always the client's version. Operators of headless hosts (a Linux VM, a spare Mac) are left copyingnpx t3@<version>into a terminal. Forks that publish their own archives also cannot point the runtime at their releases without a mirror.This change:
T3CODE_RELEASE_REPOSITORY(defaultpingdotgg/t3code), honored by the release index,t3 update, the boot service, remote self-update, and the install scripts. Downloads follow it unlessT3CODE_RELEASE_BASE_URLsets a mirror.T3CODE_UPDATE_CHECK_ENABLED=false) and advertise a strictly newer release as an optionalavailableServerUpdate: { version }field on the environment descriptor.t3@X is not published in <repository> releases.) instead of a generic prepare failure.No protocol, launcher, or rollback changes; the descriptor field is optional, so old clients and old servers are unaffected.
🤖 Generated with Claude Code