Skip to content

feat(dev-fleet): pin a worktree to the newest stable release - #23372

Open
cixuuz wants to merge 1 commit into
kirodotdev:mainfrom
cixuuz:feat/dev-fleet-release-channel-pin
Open

cixuuz wants to merge 1 commit into
kirodotdev:mainfrom
cixuuz:feat/dev-fleet-release-channel-pin

Conversation

@cixuuz

@cixuuz cixuuz commented Oct 23, 2026 •

Copy link
Copy Markdown
Contributor

Problem / Motivation

Goal: From Dev Fleet, check out the newest published stable release as a worktree you can pod or make live, with one click.

You cannot click through a shipped release from Dev Fleet. The fleet lists feature
worktrees, and every one of them is unreleased code. To see what stable actually
shipped you leave the dashboard, find the tag by hand, and make a checkout yourself.
Dev Fleet manages git checkouts, and a git checkout has no release channel.

This was built once as #10066 and never merged: its one open gate was a
security-fenced race between the probe that inspected git config and the populate
that ran after it (#12342). It was closed in favour of #12356, which depended on it
and was itself closed unmerged.

Why it matters

A bug report says "broken in stable". Answering it means running stable. Today that
is manual work outside the tool that exists to manage checkouts, so it mostly does
not happen, and a fix gets tested against main instead of against the build the
reporter is on.

Not a goal

  • A second channel. nightly tags nothing, so its row would be main; insider tags resolve, but ordering -insider.N against -rc.N needs a rule this repository does not state. CHANNEL is one "stable" constant.
  • Moving the pin in place. A newer release is Remove, then Create.
  • Changing which channel this install updates from. Nothing reads or writes $KIROCREW_HOME/channel.
  • Hardening any other checkout Dev Fleet or the dashboard performs. The hermetic populate is Create's.

Backwards compatibility

Compatible: the /fleet payload gains one key and worktrees keeps its shape; one route is added; nothing that reads today's payload or routes changes meaning.

Removes nothing: the diff adds a module, a route, a row and a prune exemption, and narrows no existing contract.

Two costs operators see: Prune merged no longer preselects any detached worktree, so a hand-detached tree stays until removed by hand; and every refresher cycle also runs a best-effort, unforced, tag-only fetch (refs/tags/*:refs/tags/*, never --tags, which would drag the branch refspec in) into the repo's own refs/tags/, so a release tag's name is one the operator can act on.

What changed (motivation → approach → change)

Dev Fleet grows one row, release-channel-stable: a real worktree detached at the
newest published stable release. Pods and Make Live already drive any fleet row, so
a shipped build boots the same way a feature branch does.

Moving part What it does
release_channel_pin.py resolves the channel tip: newest bare vX.Y.Z tag by semver, read from a private pruned tag mirror (refs/dev-fleet/release-tags/*), never from refs/tags/
worktree_ops_release.py the Create mutation, a new owner behind the worktree_ops facade: refuse if the reserved name is held, fetch the mirror, resolve, ask the remote for exactly that tag's oid and refuse any other, worktree add --no-checkout --detach at a staging path, populate hermetically, adopt with worktree move
fleet_state.py publishes the row as its own release_channel key: adopted, placeholder, unpublished, taken, error; two registered checkouts carrying the reserved name adopt nothing and the row's error says so, since the page binds channel treatment to rows by name; a tombstoned removal of the channel's worktree demotes the key to its placeholder shape in the same reconciliation that drops the row
http_api.py, server.py POST /api/release-channel/create, audited with the reserved name as its static target. It answers 202 {run_id} as soon as the create is running: the fetch, add, populate and move carry timeouts in the minutes and the app proxy drops a request at 30 s (routes_proxy._PROXY_TIMEOUT), so an awaited create could never deliver its result. The page polls /api/run?id= and reads result off the record; a refusal decidable without a run (exists, ambiguous_name, busy) is still an immediate 409 with its code, and busy names the running create so a reloaded page attaches to it. A second audit entry records how the run ended
DevFleetPageReleaseChannel.tsx, DevFleetPageRow.tsx, devFleetPageModel.ts the row and its copy: a version badge carrying currency, n/a for PR and Behind, a placeholder with Create, not on a release for a detached tree on no tag and version unknown when the release could not be read, and the reserved-name explanation when a branch checkout holds it
worktree_ops.py prune never offers empty for a detached worktree, so the pin cannot be auto-reaped; the 60-second refresher fetches the tag mirror beside the base branch every cycle (tags are small refs, transferred once), so the badge follows a new release between Creates instead of only inside one
runtime.py, app.json, appManifest.ts _start_task_run: an in-process coroutine tracked as a run in the same _RUNS registry and _ACTIVE_RUNS shutdown set as the subprocess runs, with the record's birth shared through _admit_run_locked; _error_detail: the strip-scrub-bound of a git stderr, spelled once in the module already classified for that output path, so the new owners add no redaction call site and security_posture.py is untouched (every other string in a create's result — a path, a ref — is scrubbed by /api/run, the registered egress, as it leaves the process); and the app's feature line

The populate is hermetic. Checking a tag out runs whatever filter.<name>.*
driver the repository's .git/config names, and that file is writable by anything
with the agent's filesystem access; core.worktree from the same file redirects
where the files land. #10066 probed for both and then populated, two spawns with a
writable file between them. Here the staging worktree is filled by one
git read-tree -u --reset <oid> spawn whose GIT_COMMON_DIR is a fresh,
agent-owned common dir: mkdtemp (0700), a two-line config, a HEAD, and
objects/info/alternates naming the repository's object store, with GIT_DIR,
GIT_WORK_TREE, GIT_CONFIG_NOSYSTEM=1 and GIT_CONFIG_GLOBAL=/dev/null set. That
spawn never opens the repository's .git/config or info/attributes, so nothing
written there, at any instant, can name a driver it executes or move where it
writes. No probe is left to race. Measured on git 2.54 with a required
filter.evil.smudge and a core.worktree planted: a plain populate ran the driver;
the hermetic one did not, and wrote only into the staging path. What remains is the
operator's own config acting on later ordinary reads, as on every fleet row.

flowchart LR
  subgraph Before
    A1[want to run stable]:::ctx --> B1[leave the dashboard]:::removed
    B1 --> C1[find the tag, clone by hand]:::removed
  end
  subgraph After
    A2[want to run stable]:::ctx --> B2[Create on the stable row]:::added
    B2 --> C2[detached worktree at v0.11.0]:::added --> D2[Pod / Make live]:::ctx
  end
  classDef added fill:#DCFCE7,stroke:#16A34A,color:#14532D,stroke-width:2px
  classDef removed fill:#FEE2E2,stroke:#DC2626,color:#7F1D1D,stroke-dasharray:4 3
  classDef ctx fill:#E0F2FE,stroke:#0284C7,color:#0C4A6E
  linkStyle 0,1 stroke:#DC2626,stroke-dasharray:4 3
  linkStyle 2,3,4 stroke:#16A34A,stroke-width:2px
Loading

The mirror is the integrity story for what the row shows. The operator writes
refs/tags/ too, and a local v99.0.0 would sort newest; resolution reads only the
pruned mirror (fetched with --no-prune-tags said explicitly, so a fetch.pruneTags
in .git/config cannot turn the prune on the operator's own tags), and Create
additionally refuses an oid the remote does not advertise for that tag. The row's
ref names refs/tags/<tag> only while that local tag is the resolved commit;
a diverged or absent local tag gets the mirror ref instead. Adoption is bracketed:
worktree move into an existing directory nests rather than refuses, so the
destination is re-checked under the lock before the move and the landing's admin dir
is verified after it. A successful adoption bumps a fleet epoch so a rebuild already
in flight re-reads rather than publishing the pre-create snapshot.

The row says less when it knows less. Currency lives on the version badge
(0.5.0 · latest 0.5.3, or the bare version at the tip); PR and Behind read n/a,
so Behind keeps one meaning for every row that has one. While a placeholder shows,
the count reads N worktrees · 1 to create. The spec
(docs/system-specs/modules/dev-fleet.md § Release-channel worktrees) carries each
mechanism's reasoning; the feature map gains the row.
website/capture/devfleet-release-channel.* is the screenshot harness;
website/src/i18n carries the copy in every locale (en.manual.json the source,
en.context.json the translator notes, pluralKeys.json the plural key).

Tests

  • test/test_dev_fleet_release_channel_pin.py (63): mirror refspec, --prune and --no-prune-tags, one fetch at a time, bare-tag semver ordering, MirrorOverflow, resolve_tip() states (ok / unpublished / error), the published ref for an agreeing, diverged and absent local tag, worktree_state() (detached, on a branch, unreadable HEAD, and a symbolic-ref probe that timed out or failed with a readable HEAD, which stays unknown rather than being adopted as detached).
  • test/test_dev_fleet_release_channel_ops.py (83), real git where the claim is about git: the Create sequence and each refusal code; provenance through an annotated tag's peel line; the hermetic populate with a required smudge driver and core.worktree planted before Create (test_the_hermetic_populate_runs_no_repo_declared_filter_and_writes_only_into_staging); the same populate against a sha256 repository, which the private common dir reads as sha256 because it declares the store's object format (the sha1-shaped control cannot resolve one oid); a --filter=blob:none clone is refused as partial_clone before the fetch, the add and the populate, while a hydrated clone that keeps promisor = false behind is not; a --points-at listing that fails to read becomes the row's error plus version_unknown (the badge then reads "version unknown", never "not on a release") and the partial-clone remedy, run verbatim against the fixture clone, clears the refusal; the private common dir is minimal, points at the real object store, and is deleted after success, failure and cancellation; a failed create never force-removes the lane path; a directory appearing at the lane path is refused before the move, and a move that nested into a foreign directory is undone by the nested path and reported; a successful create marks the fleet adopted and a build that predates an adoption reads git again; prune never offers a detached tree and still offers a branch checkout holding the reserved name; two checkouts sharing the reserved name adopt neither and probe neither; every emitted refusal code is classified for the route; the two owners carry no redactor call site, git stderr is scrubbed by runtime._error_detail and every other result string by /api/run; the route answers the run id before the create finishes, the record carries the result, a second start names the run in flight — including while the run body holds the worktree lock — and a tombstoned removal demotes the channel key to its placeholder, the terminal audit entry splits outcomes as the response did, and a task run that raises, is cancelled, or is refused at shutdown ends like a subprocess run.
  • test/test_dev_fleet_component_boundaries.py: the new owner joins the facade's ownership DAG and the new route joins the audited-route list.
  • website/src/test/DevFleetPageReleaseChannels.test.tsx (40): every row state above, the n/a cells, the count label, the Create click posting {} and refetching, the outcome read from the run record (success toast, a failure the run reports, a busy 409 attached to rather than reported, a run the gateway no longer knows reported as lost), refusal kept on the row as an ErrorNotice.

Determinism proof (testing-conventions § Proving a determinism fix): the two backend files green 20/20 at -n0, 10/10 at -n 4 --dist loadgroup with test_dev_fleet_app_guards.py and test_dev_fleet_component_boundaries.py as co-tenants, and 164/164 (with test_dev_fleet_app_fleet_cache.py) in a shuffled order (seed=23370). The frontend file green 10/10 repeats and shuffled (--sequence.seed=23344); its fixture clock is pinned (FIXED_NOW). Flake-pattern ratchet: zero new shapes against the merge-base.

Manual verification

  • Hermetic populate measured by hand on git 2.54 (Apple Git-157): control read-tree -u in a staging worktree ran the planted filter.evil.smudge (marker file written); the GIT_COMMON_DIR populate did not, core.worktree's redirect dir stayed empty, and the files landed in the staging path with HEAD at the oid.
  • Local gates: isort, flake8, mypy (29 files) clean; tsc -p tsconfig.app.json clean; eslint clean on every changed frontend file; i18n:check 19/19 PASS against origin/main; bundle-size gate within budget with no ceiling change; docs-lint and the feature-map check at 0.
  • Not exercised end to end against a live gateway: Create against the real kirodotdev/KiroCrew remote and a pod of the resulting worktree. The real-git tests stand in for the first; the pod path is unchanged.

Screenshots / video

Committed under temp-screenshots/release-channel/ (fork PR; --attach is unavailable to a read-access account). Captured by website/scripts/capture-devfleet-release-channel.mjs, which asserts each scene's state before shooting.

Placeholder row offering Create, count reads 2 worktrees · 1 to create

Adopted row behind the tip: badge reads 0.5.0 · latest 0.5.3, PR and Behind read n/a

More states

At the tip

Unpublished: no release fetched yet, Create live

Reserved name held by a branch checkout

After Create: toast names the release and the next control

A refused Create stays on the page, beside the channel group, and survives the refetch that may replace the placeholder with the created row

Create in progress: the button reads Creating… while the page polls the run

Resolver error on the placeholder: Create disabled, the git failure in the shared ErrorNotice

Resolver error on an adopted row: the tree keeps its own release, the notice names the failure

Reserved directory whose HEAD could not be read: ordinary row, error in the notice

Detached at no release tag: badge reads not on a release

The release probe failed on an adopted row: badge reads version unknown, never not on a release; the probe error in the notice

Row menu of the channel row: pod, Make live and QA; no Rebase

Related Issues

Closes #23344
Closes #12342

Prior review record: #10066 (closed, unmerged). Its three UX advisory copy items (#12355, closed) are folded into this copy.

Checklist

  • At most two commits (one is the norm), with a Conventional Commits title (feat|fix|docs|style|refactor|perf|test|chore|ci|build|revert: ...)
  • Existing tests pass and new tests added for new functionality
  • Self-review completed; code follows project style guidelines
  • Documentation updated (if applicable)
  • No secrets, credentials, or internal references in the diff

@cixuuz
cixuuz requested review from a team and KiroCrewCaptain as code owners October 23, 2026 07:58
@cixuuz
cixuuz requested a review from iamwhatever October 23, 2026 07:58
@github-actions github-actions Bot added fork Pull request from a fork (external contributor) readiness: checking Automated validation is still running readiness: action required A blocking check or review needs attention and removed readiness: checking Automated validation is still running labels Oct 23, 2026
@cixuuz
cixuuz force-pushed the feat/dev-fleet-release-channel-pin branch from 4dc72aa to c43cac8 Compare October 23, 2026 08:11
@github-actions github-actions Bot added readiness: checking Automated validation is still running and removed readiness: action required A blocking check or review needs attention labels Oct 23, 2026
@github-actions

github-actions Bot commented Oct 23, 2026 •

Copy link
Copy Markdown
Contributor

Opus 5.5 Review (fork) — ✅ no blocking findings

Reviewed 0d3cb9cff952dd86e8e53998e29af13d4d158675 via the fork AI-review pipeline; updated in place on each push.

Review details

No findings.

[OPUS-REVIEWED] 0d3cb9c

@github-actions

github-actions Bot commented Oct 23, 2026 •

Copy link
Copy Markdown
Contributor

Design Review (Opus 5.5, fork) — ✅ PASS

Design-level review of 0d3cb9cff952dd86e8e53998e29af13d4d158675 via the fork AI-review pipeline — updated in place on each push. A BLOCK verdict blocks PR readiness; PASS does not.

Design-Verdict: PASS

No design-level blocker. The feature solves a real problem, the hermetic one-spawn populate removes the probe-then-populate race, and the prune and refresher costs are stated in the description.

Notes

  • With the detached-tree empty exemption, abandoned hand-detached trees pile up for good. A follow-up issue that pins the abandonment policy would keep this stated cost from going unnoticed.
  • The refresher now runs a tag fetch into refs/tags/ every 60 s on every Dev Fleet install, even one that never uses the channel. That is reversible, but it changes the operator's repo state without being asked, so it should be documented in the dev-fleet spec, not just the PR body.

[DESIGN-REVIEWED] 0d3cb9c

Description read

[DESCRIPTION-READ] 57691abc0644dc256f962259d140a38a90ea5c9b6469bde1c01ed659dcb12ef6

That sha256 names everything this verdict read: the PR description, plus the 1 evidence file(s) the workflow collected from it and handed this review. It covers that evidence because the media strip replaces every attachment URL with the same placeholder, so an edit swapping one attachment for another would leave the description's captured text identical while what this review judged changed. Recompute it with .github/scripts/pr-description-capture.sh over the same inputs; if it differs, the description or that evidence changed after this verdict was formed and any finding drawn from either is unproven.

@github-actions

github-actions Bot commented Oct 23, 2026 •

Copy link
Copy Markdown
Contributor

UX Review (Opus 5.5, fork) — ✅ PASS

UX-level review of 0d3cb9cff952dd86e8e53998e29af13d4d158675 via the fork AI-review pipeline — updated in place on each push. A BLOCK verdict blocks PR readiness; PASS does not.

UX-Verdict: PASS

Nothing blocks. The 14 committed screenshots cover every new row state and control, but no first-time reader has seen them, because this fork lane has no blind read. Pushing the branch to this repository runs one.

Notes

  • In attachments 06, 07 and 12, release_channel_unresolved and frameCreateFailure put raw git stderr after the framed head (git ls-remote --tags: fatal: unable to access…, and in 11 use 'add -f' to override). The head should say what to do next, for example "check your network, then Refresh". The git text belongs in the Ask-the-agent hand-off, not in the visible line.
  • Attachment 14 shows the backend's version-unknown error: "…could not be read; refusing to name the release this tree holds". Since the badge already says version unknown, the notice should give a next step, such as removing and re-creating the worktree, instead of naming internal behaviour.
  • The behind badge (0.5.0 · latest 0.5.3, attachments 01 and 13) and the release_channel_on_older_release tooltip state the gap but give no way to close it. The row menu in 13 has no Remove, and the fix (Remove, then Create) is reached only through the detail panel. A short "Remove, then Create to update" in the tooltip would make it findable.

[UX-REVIEWED] 0d3cb9c

Description read

[DESCRIPTION-READ] f6d314467a115dbd57cb8bbcf8571b1dcd69bc341e2a86e9afc66964b99fb930

That sha256 names everything this verdict read: the PR description, plus the 17 evidence file(s) the workflow collected from it and handed this review. It covers that evidence because the media strip replaces every attachment URL with the same placeholder, so an edit swapping one attachment for another would leave the description's captured text identical while what this review judged changed. Recompute it with .github/scripts/pr-description-capture.sh over the same inputs; if it differs, the description or that evidence changed after this verdict was formed and any finding drawn from either is unproven.

@github-actions

github-actions Bot commented Oct 23, 2026 •

Copy link
Copy Markdown
Contributor

First Principles Review (Opus 5.5, fork) — ✅ PASS

Premise-level review of 0d3cb9cff952dd86e8e53998e29af13d4d158675 via the fork AI-review pipeline — why this exists and whether the shipped surface is the smallest honest version. Updated in place on each push. A BLOCK verdict blocks PR readiness; PASS does not.

First-Principles-Verdict: PASS

Nothing blocks. One note: a rider in the refresher now writes the operator's own refs/tags/ every 60 seconds on every Dev Fleet install, whether or not anyone uses the stable row.

Notes

  • Item 6 — rides along / oversized: _OPERATOR_TAG_REFSPEC = "refs/tags/*:refs/tags/*" runs as a second, unchecked fetch on every refresher cycle (1 call site, in fetch_refs). The PR already handles a missing local tag: "a diverged or absent local tag gets the mirror ref instead" (_ref_the_operator_can_act_on). Without this fetch, the row's ref just reads refs/dev-fleet/release-tags/vX.Y.Z, which still resolves. Subtraction: delete the courtesy fetch and _OPERATOR_TAG_REFSPEC, and let the existing mirror-ref fallback name the ref.

What this change ships

Inventory (10 items) — 9 justified

Intent: from Dev Fleet, check out the newest stable release as a worktree you can pod or make live, in one click. This is an ADDITION.

  1. A new release-channel-stable row in Dev Fleet: placeholder, adopted, taken, unpublished and error states, plus a version badge — justified
  2. A one-click Create that makes a detached worktree at the newest stable tag (POST /api/release-channel/create) — justified
  3. Create runs in the background as a run the page polls via /api/run, because the app proxy has a 30 s timeout (_start_task_run) — justified
  4. Create fills the worktree hermetically, so filter drivers in .git/config do not run — justified
  5. A private, pruned tag mirror, fetched every 60 s by the refresher — justified
  6. Every refresher cycle also fetches all remote tags into the operator's own refs/tags/ — rides along, oversized
  7. Prune merged no longer preselects any detached worktree as empty — justified
  8. Create refuses a commit the remote does not advertise for that tag — justified
  9. Two audit entries per Create: one at start, one for how it ended — justified
  10. A new feature line in the app manifest and new row strings in 14 locales — justified

[FIRST-PRINCIPLES-REVIEWED] 0d3cb9c

Description read

[DESCRIPTION-READ] 42f55b126506182377e2e1bd9680f671c64ff50caedf3e69c9e266c940691070

That sha256 names the PR description this verdict read. Recompute it with .github/scripts/pr-description-capture.sh; if it differs, the description changed after this verdict was formed and any finding drawn from the description is unproven.

@github-actions github-actions Bot added readiness: action required A blocking check or review needs attention and removed readiness: checking Automated validation is still running labels Oct 23, 2026
@github-actions

github-actions Bot commented Oct 23, 2026 •

Copy link
Copy Markdown
Contributor

GPT 6.1 Review (fork) — ✅ no blocking findings

Reviewed 0d3cb9cff952dd86e8e53998e29af13d4d158675 via the fork AI-review pipeline; updated in place on each push.

Review details

No findings.
[GPT-REVIEWED] 0d3cb9c

@cixuuz
cixuuz force-pushed the feat/dev-fleet-release-channel-pin branch from c43cac8 to 9cf097a Compare October 23, 2026 08:29
@github-actions github-actions Bot added readiness: checking Automated validation is still running readiness: action required A blocking check or review needs attention and removed readiness: action required A blocking check or review needs attention readiness: checking Automated validation is still running labels Oct 23, 2026
@cixuuz
cixuuz force-pushed the feat/dev-fleet-release-channel-pin branch from 9cf097a to d7edf2d Compare October 23, 2026 08:43
@github-actions github-actions Bot added readiness: checking Automated validation is still running readiness: action required A blocking check or review needs attention and removed readiness: action required A blocking check or review needs attention readiness: checking Automated validation is still running labels Oct 23, 2026
@cixuuz
cixuuz force-pushed the feat/dev-fleet-release-channel-pin branch from d7edf2d to c98578d Compare October 23, 2026 09:10
@github-actions github-actions Bot added readiness: checking Automated validation is still running and removed readiness: action required A blocking check or review needs attention labels Oct 23, 2026
@cixuuz

cixuuz commented Oct 24, 2026

Copy link
Copy Markdown
Contributor Author

Rebased: 52411b88f25a98a8826dfeb83b1ed890a00d77e1 -> 31cf3605d3363c5cd3e67a237c59b6ef95aff238 onto 8f317e5f (main +43; overlap in the feature-map README and the i18n catalogs; #23634 adds @rolldown/plugin-babel to the frontend toolchain). Clean rebase, no source change. On 52411b88f2 every check was green except the fork GPT 6.1 lane, whose run (40091304244) failed at the configure-aws-credentials step before any review step ran, so there is no verdict for that head and PR Readiness failed closed behind it — infrastructure, not a finding. This account cannot rerun the lane; the push is what re-triggers it. Local on the rebased tree: 1504 dev-fleet backend tests + both ratchets + facade guard, flake8, mypy (30 files), docs-lint, feature-map, tsc, 247 DevFleet vitest, 15 i18n checks + 1035 i18n tests base-scoped, body 446 words, guard single-commit-on-base.

@cixuuz
cixuuz force-pushed the feat/dev-fleet-release-channel-pin branch from 52411b8 to 31cf360 Compare October 24, 2026 22:40
@github-actions github-actions Bot removed the readiness: action required A blocking check or review needs attention label Oct 24, 2026
@cixuuz

cixuuz commented Oct 24, 2026

Copy link
Copy Markdown
Contributor Author

Rebased: 31cf3605d3363c5cd3e67a237c59b6ef95aff238 -> bf820fd26c893ee17ed2ef6bf95ddcd388e55766 onto 1a7190c711 (main +2: #23271, #23622; feature-map README overlap). Clean rebase, no source change; feature-map, docs-lint, 138 release-channel tests, guard single-commit-on-base.

Fork GPT 6.1 lane, second consecutive infrastructure failure, different step each time: on 52411b88f2 it died at configure-aws-credentials (run 40091304244); on 31cf3605d3 it died at Fetch authentic diff with could not read the merge-base of 8f317e5f and 31cf3605d3; failing closed (run 40139679215). Neither reached a review step, so neither is a finding; the lane last produced a verdict on 128827ceb8 (no blocking findings), and the source is unchanged since. This push re-triggers the lane. If it fails a third time it is the lane, not this PR, and a maintainer would need to look at the fork GPT workflow.

@cixuuz

cixuuz commented Oct 24, 2026

Copy link
Copy Markdown
Contributor Author

Opus — release_channel_pin.py "the refresher now fetches every branch of the remote every 60 seconds" (span cabfbe3dd967): fixed in 67f87b3a64.

Confirmed against real git before changing anything: in a clone with the default +refs/heads/*:refs/remotes/origin/*, git fetch --no-prune --no-prune-tags --tags origin created origin/newbranch; git fetch --no-prune --no-prune-tags origin refs/tags/*:refs/tags/* did not, and brought in the new tag while leaving a diverged local tag untouched. The finding is correct and the spec claim it contradicts was wrong as written.

The courtesy fetch now names _OPERATOR_TAG_REFSPEC = "refs/tags/*:refs/tags/*" (unforced, no +) instead of --tags, so it moves tags and nothing else. --no-prune --no-prune-tags stay, and the comment now says why they matter more with an explicit refspec (its destination is what a pruning fetch would prune). fetch_refs docstring, the spec (dev-fleet.md, four passages) and the body's Backwards-compatibility cost are updated to describe the refspec, not --tags. Tests: test_the_courtesy_fetch_moves_tags_and_nothing_else (no --tags, refspecs == the tag refspec only, unforced, no branch named), plus the existing courtesy-fetch tests re-keyed on the refspec.

@cixuuz

cixuuz commented Oct 24, 2026

Copy link
Copy Markdown
Contributor Author

GPT F1 — DevFleetPage.tsx "Fleet refresh removes the only notice for a refused Create" (span bf06a3b651f2): fixed in 67f87b3a64.

Confirmed: releaseChannelCreateError was rendered only inside ReleaseChannelPlaceholder, and the refetch after a 409 exists (a stale tab) makes channelPlaceholderFor return null, unmounting the notice with its cause, recovery steps and hand-off. The existing test only covered a refetch that returned the same placeholder payload.

The notice is now its own component, ReleaseChannelCreateError, rendered by the page beside the channel group and keyed on releaseChannel.lane, so it survives whichever row shape the channel has after the refetch and goes only on retry or dismiss. ReleaseChannelPlaceholder no longer takes createError/onDismissCreateError. New test keeps the refusal notice when the refetch replaces the placeholder with the created row: first /fleet serves the placeholder, Create answers 409 exists, every later /fleet serves the created worktree; asserts the placeholder is gone, the adopted row is up, the notice is still present with the cause and Ask the agent, and dismiss works from the new shape. Owners test (file structure) still green; body caption for frame 11 updated.

@cixuuz

cixuuz commented Oct 24, 2026

Copy link
Copy Markdown
Contributor Author

Rebased: bf820fd26c893ee17ed2ef6bf95ddcd388e55766 -> 67f87b3a646f5c6eb4b627e303def3cb10a5fdf3 onto 0dab198632 (main +4; no overlap). Carries the two review fixes above (tag-only courtesy refspec; Create-failure notice hoisted out of the placeholder).

Bundle Size Gate on bf820fd26c is a red main, not this PR. assets/ModelEffortDropdown-*.js is 519.85 kB against a 500 kB default budget on a clean origin/main build as well (checked in a scratch worktree of 0dab198632; same size, same gate failure, rc 2). This PR touches nothing in that chunk's import graph (ModelEffortDropdown.tsx, useChatPanePickers.ts). It will clear when main does; not adjusting CHUNK_BUDGETS here.

Local on the rebased tree: 1505 dev-fleet backend tests + ratchets + facade guard, flake8, mypy (30 files), isort, docs-lint, feature-map, tsc, eslint, 248 DevFleet vitest, 15 i18n checks + 1035 i18n tests base-scoped, body 446 words, guard single-commit-on-base. The fork GPT lane reached a verdict on bf820fd26c after two infra failures, so the lane is working again.

@cixuuz

cixuuz commented Oct 25, 2026

Copy link
Copy Markdown
Contributor Author

Opus — worktree_ops_release.py "hermetic common dir declares no object format and no promisor remote" (span ca6351073d65): fixed in ac43c25c15.

Both arms reproduced with real git before any change: against a sha256 repository the sha1-shaped common dir answered fatal: Not a valid object name <64-hex> (rc 128); the same populate with repositoryformatversion = 1 + extensions.objectformat = sha256 landed the tree. Against a --filter=blob:none clone the populate died with unable to read sha1 file of p.txt on the first missing blob.

Object format — fixed as suggested. _hermetic_paths now also reads git rev-parse --show-object-format from the real store and _make_hermetic_common_dir writes _hermetic_common_config(format): the existing two-line config for sha1, or version 1 plus extensions.objectformat for sha256. An answer that is neither fails closed before the populate runs. The sha1 shape is byte-identical to before, so no behaviour changes on the common case.

Partial clone — refused up front, not backfilled. Of the two remedies offered, refusal is the proportionate one: the populate runs on the no-network tier on purpose (lazy fetching is exactly the kind of implicit network call the hermetic design keeps out), and a promisor remote is checkout state the operator chose and can change. _release_channel_create_locked probes git config --get-regexp for remote.<name>.promisor / extensions.partialclone right after the path check and before the fetch, and answers the new classified refusal partial_clone (409, listed in _RELEASE_CHANNEL_REFUSALS) naming the remedy. Nothing is fetched and nothing touches disk. An unreadable config answers "not partial" so the populate fails on git's own terms rather than this probe guessing.

Tests (4 new, two real-git): test_the_hermetic_populate_reads_a_sha256_store_as_sha256 (both arms, one repo; the sha1-shaped control is asserted to fail), test_create_refuses_a_partial_clone_before_it_fetches_or_touches_disk (a real blob:none clone; asserts no fetch/worktree/read-tree spawned and no directory created), test_a_full_clone_is_not_mistaken_for_a_partial_one, test_the_populate_declares_the_stores_object_format_to_the_private_common_dir. Facade manifest, spec (dev-fleet.md: the GIT_COMMON_DIR row, a new partial-clone paragraph, the refusal list) and body updated.

@cixuuz

cixuuz commented Oct 25, 2026

Copy link
Copy Markdown
Contributor Author

Rebased: 67f87b3a646f5c6eb4b627e303def3cb10a5fdf3 -> ac43c25c151a3e0c9dc74c335a6e7d3277d171ee onto ec2619c6f3 (main +3; feature-map README overlap). Carries the Opus fix above. Pushed without waiting for the fork GPT lane on 67f87b3a64: its run (40142791484) had sat on Resolve and validate PR for 34 minutes without opening a check-run, so there was no verdict to wait for and the source it would judge is replaced by this push. Bundle Size Gate is still the red main (ModelEffortDropdown chunk over its 500 kB default on clean origin/main). Local on the rebased tree: 1531 dev-fleet backend tests + ratchets + facade guard, black/isort/flake8/mypy, docs-lint, feature-map, body 446 words, guard single-commit-on-base.

@cixuuz

cixuuz commented Oct 25, 2026

Copy link
Copy Markdown
Contributor Author

Rebased: ac43c25c151a3e0c9dc74c335a6e7d3277d171ee -> f0badb1c3c5a8280254e452152ab020462bf6085 onto 3d6764bf31 (main +2; no overlap). No source change.

The round on ac43c25c15 was a GitHub API read window, not code: 18 check-runs failed or were cancelled within a minute of the push, each with the same cause in its own words — Screenshot Evidence "Could not read this PR's description from the API after 3 attempts … a read failure, not missing evidence — re-run this job", Sensitive Change Review "Could not read the PR from the API", the fork GPT lane at Resolve and validate PR, Security Scope "the review scope was never resolved", Fork PR Description, Dependency License Gate (which also reported "Dependency review is not supported on this repository", as it did for the unrelated head 7c6fcf628b in the same window). The PR's mergeable was null at the time and is true again now; heads pushed three minutes later passed the same gates. This account cannot rerun jobs, so the push is the re-trigger; it restarts every gate rather than one workflow at a time.

@cixuuz

cixuuz commented Oct 25, 2026

Copy link
Copy Markdown
Contributor Author

GPT F1 — worktree_ops_release.py "A disabled promisor remote still blocks Create" (span 832181c6170a): fixed in c9763827cb.

Correct, and a defect in the fix I landed one head earlier. Reproduced with real git: a blob:none clone that drops its filter and runs git fetch --refetch has zero missing objects but keeps remote.origin.promisor = false, and my --get-regexp probe matched on the key's presence. _is_partial_clone now hands the output to _promisor_in_force, which parses remote.<name>.promisor as a git boolean (true/yes/on/1, and a bare key with no value) and treats extensions.partialclone — which holds a remote NAME — as partial for any value. Test test_a_hydrated_clone_with_promisor_false_is_not_partial covers the hydrated shape end to end (the create is not refused), every true/false spelling, the legacy extension, and several remotes with one in force. Spec's partial-clone paragraph updated to say "in force", not "set".

@cixuuz

cixuuz commented Oct 25, 2026

Copy link
Copy Markdown
Contributor Author

GPT F2 — release_channel_pin.py "A failed version lookup falsely reports not on a release" (span 7e89de79d6c1): fixed in c9763827cb, although adjudication downgraded it.

Correct: repository._git folds a non-zero exit into None, which _release_at_head returned as "no release here", so a timed-out for-each-ref --points-at HEAD published version: null with no error and the badge read "not on a release" for an unchanged release checkout. The fix was one seam and rode a path that already existed: _release_at_head now runs the listing through runtime._run_cmd directly and raises ReleaseLookupFailed on a non-zero exit (an empty listing with exit 0 is still the real "on no release" answer), and _release_channel_tree_state catches it beside MirrorOverflow — the tree stays adopted, version is null, and the row's error says the release could not be read. Test test_a_failed_points_at_listing_is_the_rows_error_not_no_release asserts both arms. Spec: one sentence added to the version paragraph.

@cixuuz

cixuuz commented Oct 25, 2026

Copy link
Copy Markdown
Contributor Author

Rebased: f0badb1c3c5a8280254e452152ab020462bf6085 -> c9763827cb2f25a0d7eca727b8d441b3ce7ce0a7 (base unchanged at 3d6764bf31). Carries the two GPT fixes above. Opus passed f0badb1c3c with no blocking findings, so the sha256 / partial-clone change from the previous head stands. Local: 1533 dev-fleet backend tests + ratchets + facade guard, black/isort/flake8/mypy, docs-lint, feature-map, body 446 words, guard single-commit-on-base.

@cixuuz

cixuuz commented Oct 25, 2026

Copy link
Copy Markdown
Contributor Author

GPT F1 — DevFleetPageReleaseChannel.tsx "Failed version lookup misclassifies a release checkout" (span ed9a4959fb53): fixed in d273ffa6da.

Correct: the previous head fixed the backend half (the failed listing became the row's error) but the badge still rendered every version: null as "not on a release", so the PR's own documented promise was not yet kept on screen. As the adjudicator sized it — one flag and one string. The row now carries version_unknown (true only when the --points-at probe itself failed; false on the positive answer, on every placeholder, and after a removal demotes the row), and ReleaseChannelBadge reads "version unknown" with a tooltip pointing at the row's ErrorNotice instead of asserting a state about a tree nobody read. Two new i18n keys in all 12 catalogs (release_channel_version_unknown, _title), pseudolocale regenerated, 15 i18n checks + 1035 base-scoped i18n tests pass. Tests: backend test_a_failed_points_at_listing_is_the_rows_error_not_no_release extended to assert the flag on both arms, the placeholder and the demotion; frontend says "version unknown", never "not on a release", when the release probe itself failed. Spec paragraph updated.

@cixuuz

cixuuz commented Oct 25, 2026

Copy link
Copy Markdown
Contributor Author

GPT F2 — worktree_ops_release.py "Partial-clone recovery leaves Create permanently refused" (span 832181c6170a, second round): fixed in d273ffa6da.

Correct, and this closes the span rather than patching around it: the previous round made the probe read promisor by value, and this round's finding is that my own remedy text never told the operator to clear that value. Reproduced with real git: unset partialclonefilter + fetch --refetch leaves zero missing objects but remote.origin.promisor = true, so the probe refuses again. The message now gives the three steps in the order git needs them — unset the filter, --refetch, then git config --unset remote.<name>.promisor (and extensions.partialclone if set) — or a full clone. The real-git test test_create_refuses_a_partial_clone_before_it_fetches_or_touches_disk now runs that remedy verbatim against its blob:none fixture and asserts _is_partial_clone turns False with no object missing; the message's step order is asserted too. Spec's partial-clone paragraph names the same steps.

@cixuuz

cixuuz commented Oct 25, 2026

Copy link
Copy Markdown
Contributor Author

Rebased: c9763827cb2f25a0d7eca727b8d441b3ce7ce0a7 -> d273ffa6daaa034193faaa45f6d27e14236dc378 onto 3d6764bf31. Carries the two GPT fixes above; Opus passed c9763827cb with no blocking findings. Local: 1533 dev-fleet backend tests + ratchets + facade guard, black/isort/flake8/mypy, docs-lint, feature-map, tsc, eslint, 249 DevFleet vitest, 15 i18n checks + 1035 i18n tests base-scoped, body 446 words, guard single-commit-on-base.

@cixuuz

cixuuz commented Oct 25, 2026

Copy link
Copy Markdown
Contributor Author

Rebased: d273ffa6daaa034193faaa45f6d27e14236dc378 -> 78b6f5a7eb91d8071d3bdd13c8593e8bbbc61567 onto c4ce1efa5d (main +2, no overlap). No source change. On d273ffa6da: GPT passed with no blocking findings (the two cycle-115 fixes accepted), every test shard green; the fork Opus lane reported review incomplete because its run (40148378546) failed at Fetch authentic diff — could not read the merge-base — before any review step, so there is no Opus verdict for that head; Opus last passed on c9763827cb, whose source differs only by the two GPT fixes. This push re-triggers the lane. Bundle Size Gate remains the red main (ModelEffortDropdown chunk over its default budget on clean origin/main). Local: 145 release-channel tests, docs-lint, feature-map, guard single-commit-on-base.

@cixuuz

cixuuz commented Oct 25, 2026

Copy link
Copy Markdown
Contributor Author

Rebased: 78b6f5a7eb91d8071d3bdd13c8593e8bbbc61567 -> 673cc2c0bff452444ea2d5e554c767fe6d96fe5a onto 8fdb1bb28202 (main +7, docs-only overlap in docs/system-specs/modules/, no conflict). On 78b6f5a7eb every lane passed: GPT and Opus no blocking findings, Design / UX / First Principles PASS, 103/103 checks. Three advisory notes from that round are folded into this head, each answered in its lane's disposition below:

  • First Principles item 6: _error_detail loses its limit parameter; the bound is the module constant _ERROR_DETAIL_LIMIT = 200, and the one test that passed limit= is gone.
  • UX evidence gap: new scene version-unknown in website/capture/devfleet-release-channel.tsx and frame temp-screenshots/release-channel/14-release-channel-version-unknown-dark.png, asserted by the runner (badge reads "version unknown", none of "not on a release" / a version / the tip; the probe's error in the row-scoped ErrorNotice; tooltip points at it). Linked from the description.
  • Design note on the 8000-byte cut: ## Backwards compatibility now sits before ## What changed, ending at byte 2291, so the prune cost is inside what the lane reads.

Local: 1511 dev-fleet + ratchet tests, black/isort/flake8/mypy clean, docs-lint, feature-map gate against origin/main, tsc, DevFleet vitest 249, i18n 1035 + i18n:check PASS, push guard SAFE (single commit on base, 7 frames vouched).

@cixuuz

cixuuz commented Oct 25, 2026

Copy link
Copy Markdown
Contributor Author

First Principles PASS on 78b6f5a7eb, two notes:

Item 6 (_error_detail limit knob, zero consumers): fixed on 673cc2c0bf. The parameter is deleted; the bound is _ERROR_DETAIL_LIMIT = 200 in runtime.py, and the single test line that exercised limit=10 is removed. Every one of the nine call sites is unchanged.

Item 3 (prune withholds empty from every detached tree): accepted as stated, not narrowed. The guard is deliberately shape-based, for the same reason adoption is: the pin is identified by being detached at the reserved path, and _prunable runs over the fleet listing before any row is classified as the channel. Narrowing to "the pin's admin path" would make prune depend on the channel classification that fleet_state computes later and can fail (unreadable, ambiguous_name), and a tree whose classification failed is exactly the one that must not be preselected for deletion. The cost is named in the description's ## Backwards compatibility (now within the first 2.3 KB) and in the spec. A hand-detached tree still shows in Prune's list; it is no longer preselected.

@cixuuz

cixuuz commented Oct 25, 2026

Copy link
Copy Markdown
Contributor Author

UX PASS on 78b6f5a7eb; the evidence gap and two notes:

Evidence gap ("version unknown" badge): closed on 673cc2c0bf. New scene version-unknown (website/capture/devfleet-release-channel.tsx: adopted row, version: null, version_unknown: true, the probe's error in error) and frame temp-screenshots/release-channel/14-release-channel-version-unknown-dark.png. The runner asserts the pill reads version unknown, that none of not on a release, 0.5.0, 0.5.3, latest 0.5 or latest ? is on the page, that Behind is the n/a marker, that the tooltip is the "could not be read — see the notice below the row" sentence, and that the row-scoped ErrorNotice carries the probe text. Linked from the description's "More states".

Raw git stderr after the framing text (06, 07, 12): accepted and deferred, not changed here. The framing sentence ("The stable release channel could not be resolved") is the cause in the user's words; the git text is the detail, kept because it is what the operator pastes or hands to "Ask the agent", which every ErrorNotice on this page carries. Translating each git failure class into a next step ("check your connection") is a per-class table the page does not have for any other fleet error either; adding one only for the channel row would make this row's notices differ from the rest of Dev Fleet. Same disposition as on 3fec19aa88.

Frame 11 passes git's own advice ("use 'add -f'…") through: accepted and deferred. That text is git's stderr for a dangling worktree registration, rendered by the same ErrorNotice. The step the user can take IS on the page: Prune merged lists the registration, and the notice's Ask-the-agent button hands the exact text to the agent. Rewriting git's advice would need the create path to classify stderr by message, which the backend deliberately does not do (it classifies only its own refusals: exists, ambiguous_name, busy, partial_clone).

@cixuuz

cixuuz commented Oct 25, 2026

Copy link
Copy Markdown
Contributor Author

Design PASS on 78b6f5a7eb, three notes:

_prunable withholds empty from every detached tree: accepted as the designed cost. Stated in ## Backwards compatibility ("Prune merged no longer preselects any detached worktree, so a hand-detached tree stays until removed by hand") and in the spec. The guard is shape-based on purpose; see the First Principles disposition for why narrowing it to the pin's path would tie prune to a classification that can fail.

Refresher fetches base + _TAG_REFSPEC in one argv, coupling Behind to the mirror: rebutted. The single-argv fetch is one spawn per cycle instead of two against the same remote, and both refspecs are served by the one remote the repo has; a remote that cannot serve the tag refspec cannot serve the base branch either (same transport, same auth), so the coupling adds no failure Behind did not already have. The mirror refspec is forced (+refs/tags/v*:refs/dev-fleet/release-tags/v*) into a namespace only this code writes, so it has no ref-level failure of its own (no divergence, no lock contention beyond the _FETCH_LOCK that already serialises the fetchers). The only mirror-specific failure (MirrorOverflow, more than the bounded row count) is raised when the mirror is READ, not fetched, and is carried into the row's error without touching Behind. The courtesy refs/tags/* fetch, which is the one that can fail independently (unforced, a diverged operator tag), IS a separate spawn and is best-effort.

## Backwards compatibility past the 8000-byte cut: fixed on 673cc2c0bf. The section now precedes ## What changed and ends at byte 2291 of the description.

@cixuuz

cixuuz commented Oct 25, 2026

Copy link
Copy Markdown
Contributor Author

Opus no blocking findings on 78b6f5a7eb; one note:

_drop_worktrees leaves name_taken_by_branch set for one cache TTL when the branch-held checkout is removed mid-build: accepted, not changed. The window is one TTL and self-heals on the next rebuild; the row that briefly shows neither a row nor Create is the removed one, so no control is offered on a path that no longer exists. Demoting the channel key when a branch-held row is dropped would need _drop_worktrees to know that the dropped row was the holder, which is the classification fleet_state computes from git, not from the tombstone. A successful Create or removal of the pin itself already bumps the fleet epoch, so the case that matters to the feature (the pin's own lifecycle) does not wait for the TTL.

@chenmingwei23

Copy link
Copy Markdown
Contributor

Thanks for picking this up again. Before reviewing the code, I'd like to understand the user problem better.

Who is the user here, and how often do they need a stable checkout from Dev Fleet? Today someone who needs to repro on stable can run git worktree add ../stable vX.Y.Z once. Is there evidence (issues, reports) that this step is what keeps people from testing against stable?

I'm asking because the change is large for one row (8k+ lines, two new ~700-line modules, a 530-line spec section), and it changes existing behavior for everyone who uses Dev Fleet, not only people who click Create:

  • The 60s refresher now fetches all upstream tags into the local refs/tags/ every cycle.
  • Prune merged no longer preselects any detached worktree.
  • The base-branch fetch now runs in the same git fetch as the tag mirror, so a mirror failure also leaves the base branch stale.

If the need is real, could a smaller version work? For example, a "create worktree at tag" action, without the background tag mirror and without the prune change.

Dev Fleet gains one row, `release-channel-stable`: a real git worktree
detached at the newest published stable release, so a shipped build can be
podded or made live beside unreleased branches. Create is the whole job; the
pod controls already drive any fleet row.

Resolution reads a private, pruned tag mirror (`refs/dev-fleet/release-tags/*`),
never `refs/tags/`, orders bare `vX.Y.Z` tags by semver, and refuses any oid the
remote does not advertise for that tag. `CHANNEL` is a single `"stable"`
constant: nightly tags nothing and insider ordering has no stated rule.

The populate is hermetic. The staging worktree is filled by one
`git read-tree -u --reset <oid>` spawn whose `GIT_COMMON_DIR` is a fresh,
agent-owned common dir (0700; a two-line config; `objects/info/alternates`
naming the repository's object store), with `GIT_DIR`, `GIT_WORK_TREE`,
`GIT_CONFIG_NOSYSTEM=1` and `GIT_CONFIG_GLOBAL=/dev/null` set. The repository's
own `.git/config` and `info/attributes` are never opened by that spawn, so no
`filter.<name>.*` driver or `core.worktree` redirect written there, at any
instant, can reach it. That retires the probe-then-populate sequence kirodotdev#10066
carried and the race between its two spawns.

The row says what it knows: a version badge carrying currency, `n/a` for PR
and Behind, a placeholder with Create, `not on a release` for a detached tree
on no tag, and the reserved-name explanation when a branch checkout holds it.
Prune never offers `empty` for a detached worktree.

Closes kirodotdev#23344
Closes kirodotdev#12342
@cixuuz

cixuuz commented Oct 25, 2026

Copy link
Copy Markdown
Contributor Author

Rebased: 673cc2c0bff452444ea2d5e554c767fe6d96fe5a -> 0d3cb9cff952dd86e8e53998e29af13d4d158675 onto 2719ba93 (main +1, sibling files in website/capture/ only, no conflict). One source change, from the GPT round on 673cc2c0bf (F1, downgraded on adjudication but a real defect, fixed because the remedy is three lines): worktree_state reads the symbolic-ref --quiet HEAD exit status directly instead of through _git (which folds every non-zero exit into None). Exit 0 is a branch checkout, exit 1 is git's documented detached-HEAD answer, anything else (_run_cmd's -1 for a timeout or refusal, 128 for a fatal:) leaves the shape unknown, so a branch checkout holding the reserved name whose probe timed out is no longer adopted as the pin; fleet_state then publishes "exists but its git state could not be read" on the row. Two parametrized tests (test_a_symref_probe_that_did_not_answer_leaves_the_shape_unknown, rc -1 and 128 with a readable HEAD) cover it; the _tree_git stub now carries the exit status through.

On 673cc2c0bf: Opus no findings, GPT no blocking findings after adjudication, Design / UX / First Principles PASS (notes answered below), every finished check green. Local: 1513 dev-fleet + ratchet tests, black/isort/flake8/mypy, docs-lint, feature-map gate, tsc, DevFleet vitest 249, body check, push guard SAFE (single commit on base, 7 frames vouched).

@cixuuz

cixuuz commented Oct 25, 2026

Copy link
Copy Markdown
Contributor Author

GPT on 673cc2c0bf: one finding, downgraded on adjudication. Fixed anyway on 0d3cb9cff9.

F1 (release_channel_pin.py:575, a timed-out symbolic-ref adopts a branch checkout): fixed. The finding is correct: _git returns None for every non-zero exit, so a six-second timeout on the probe was indistinguishable from a detached HEAD, and with rev-parse HEAD readable the tree was published as the pin. The adjudicator's reason for downgrading (the remedy needs a git call that keeps the exit code) turned out not to cost new mechanism: runtime._run_cmd already returns the exit status, so the probe now calls it directly. Exit 0 → attached; exit 1 (git's documented --quiet answer for a detached HEAD, verified against git 2.54) → detached; anything else (-1 timeout/refusal, 128 fatal:) → detached stays None and _adopt_release_channel_row publishes "exists but its git state could not be read" instead of adopting. Tests: test_a_symref_probe_that_did_not_answer_leaves_the_shape_unknown[-1] and [128], both with a readable HEAD; the existing _tree_git stub carries the exit status through.

@cixuuz

cixuuz commented Oct 25, 2026

Copy link
Copy Markdown
Contributor Author

Opus no findings on 673cc2c0bf; the one note is the same stale-name_taken_by_branch window as on 78b6f5a7eb: accepted, unchanged. One cache TTL, self-healing on the next rebuild, no control offered on a path that no longer exists, and the tombstone cannot know the dropped row was the holder without re-running the git classification it exists to avoid.

@cixuuz

cixuuz commented Oct 25, 2026

Copy link
Copy Markdown
Contributor Author

UX PASS on 673cc2c0bf (evidence gap from the previous round closed by frame 14); three notes:

Raw git stderr in the notices (06, 07, 11, 12): accepted and deferred, unchanged from the two previous rounds. The framing sentence is the plain cause ("The stable release channel could not be resolved"); the git text follows as the detail because it is what "Ask the agent", present on every notice, hands over. A per-failure-class rewrite table ("Can't reach GitHub — check your connection") exists for no other Dev Fleet error, and the backend deliberately classifies only its own refusals, never git's prose.

Frame 14's notice repeats itself: accepted and deferred. The text is framing + <name>: <probe error>; refusing to name the release this tree holds. The last clause is the consequence, kept because the same path also carries MirrorOverflow ("more than N refs under …"), where the consequence is not obvious from the cause. Trimming it is a copy change to a tested string shared by the backend test, the vitest fixture and frame 14's assertion; worth doing in a copy pass, not in a round that also carries a defect fix.

Frame 07 shows a red notice when only the tip lookup failed: rebutted. The tip failing means the row cannot say whether the checkout is current, which is the row's reason to exist; it also means Create (on the placeholder variant, 06) is disabled. "latest unknown" in the badge alone would put the only account of the failure in a pill with no keyboard-reachable detail and no "Ask the agent", which is the Badge-title-vs-ErrorNotice point an earlier UX round made and this design followed. The badge already stops asserting (bare 0.5.0, no latest), and the notice is the single error surface by design.

@cixuuz

cixuuz commented Oct 25, 2026

Copy link
Copy Markdown
Contributor Author

First Principles PASS on 673cc2c0bf; one note:

Item 6 (the courtesy refs/tags/*:refs/tags/* fetch and _ref_the_operator_can_act_on exist only to pick the row's ref string): rebutted, kept. The published ref is not decoration: it is the name the row offers for the operator's own git (git checkout refs/tags/v0.5.3, git log v0.5.3), and the mirror ref refs/dev-fleet/release-tags/v0.5.3 is a private namespace nothing else on the machine knows. Publishing the mirror ref always would hand every operator a ref they must learn before they can use it, and the one case the divergence probe handles (a local v0.5.3 that is NOT the released commit) is exactly where publishing refs/tags/<tag> would be wrong, so the probe cannot be dropped without also dropping the human-readable name. The cost is bounded: an unforced, tag-only, no-prune fetch of small refs against a remote the refresher already fetched a second earlier, within the same _FETCH_LOCK. Stated in ## Backwards compatibility. A previous Opus round asked for this fetch to be narrowed from --tags to the explicit refspec, which is the shape it has now.

@cixuuz

cixuuz commented Oct 25, 2026

Copy link
Copy Markdown
Contributor Author

Design PASS on 673cc2c0bf. The Backwards-compatibility section is now inside the lane's read window (ends at byte 2291); the prune-cost and refresher-coupling notes from 78b6f5a7eb keep the dispositions given there (accepted as the stated cost; rebutted, the mirror refspec is forced into a private namespace and shares the base fetch's transport).

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

fork Pull request from a fork (external contributor) readiness: checking Automated validation is still running

Projects

None yet

2 participants