Skip to content

Land: ci-only mode (LAND_CI=only), queued CI runs waited for, CI Android ABI rebaseline - #222

Merged
thejackshelton merged 19 commits into
masterfrom
land-ci-only
Oct 8, 2026
Merged

thejackshelton merged 19 commits into
masterfrom
land-ci-only

Conversation

@thejackshelton

Copy link
Copy Markdown
Contributor

Item 7 of the cloud migration plan, and R3 of its parity proof (docs/goals/milestone-2-proof/notes/cloud-migration.md). Stacked on #220 (LAND_REGEN=ci); #220's review fixes are merged in.

What changed

ci-only mode. LAND_DEVICES, LAND_TEST and LAND_REGEN each accept ci-only. LAND_CI=only sets all three, and it is the documented setting for a host other than this Mac. With LAND_CI=only set, any of the three set to local or ci is refused. All parsing is in parseLandModes (land-lib.ts), which replaces parseCiMode.

  • Under ci-only, every place where GitHub Actions doesn't run a step throws CiOutage, a new subclass of Fatal, and never runs the step locally. That covers the device lanes, the full test, the regen (landRegen and a prepared position's CI regen), and a master that has no CI workflow yet.
  • runBatches handles a CiOutage differently from other Fatal errors. It records no failed outcome, adds no label and posts no comment. It returns outage, and stopped lists every PR that has no outcome, including the batch it hit, in queue order. The status reads STOPPED BY A CI OUTAGE, names the outage, and lists those PRs as still queued.
  • A builder's outage reaches the driver as an outage (serializeFatal / parsePrepared). proveRestingMaster rethrows it, so the driver stops before any batch.
  • When all three steps are ci-only, the driver skips the priority file and the quiet-machine file. The heavy and device leases are used only on local paths, which ci-only never reaches. So the driver needs no zsh, no /opt/homebrew and no /tmp helper script.
  • Under plain LAND_DEVICES=ci, the local fallback stays, with one exception. When the previous position's Android records are from a different ABI than this host's, the driver stops instead of falling back. A local run there could only differ in architecture, so it would blame the PR.

Queued vs never started (awaitOnCi, land-devices-ci.ts). This applies to every CI wait: devices, full test and regen.

  • After LAND_CI_START, a run that exists and is waiting for runners counts as queued. That means its jobs are queued or waiting, its resolve job is still running, or the run itself is pending with no jobs yet.
  • A queued run is waited for up to LAND_CI_QUEUE_WAIT (default 3 h), then counts as unavailable.
  • A run with no job started and none waiting still counts as unavailable at LAND_CI_START, as before.
  • The run's own wait (waitS) now counts from its first job's start, so time spent queued before that is not taken from it.

Concurrent CI regens (from #220's review). LAND_CI_MAX_INFLIGHT (default 2) caps how many prepared positions dispatch a CI regen at once. Each regen takes several macOS jobs, and the free plan runs 5 at once. Positions above the cap are built one by one, and each dispatches its regen in turn.

R3: Android ABI rebaseline. This keeps LAND_ARCH_REBASELINE as it is: it applies to one named PR, needs a line in the PR's body, and allows any change of model. Under LAND_DEVICES=ci or ci-only, a CI device run whose model changes are all Android image ABI changes is now accepted as an architecture rebaseline. "ABI change" means the same model and device apart from built for <abi> or the vectors ABI (ciArchRebaseline, archChanges).

  • The classification is cross-checked against the count from modelChanges. A change the split doesn't see is never let through.
  • The verdicts are then judged by deviceRunProblems with rebaseline. Each changed lane must keep the previous state and exactly the previous failures, so no verdict difference is accepted, not even fewer failures.
  • The rebaseline is logged with a !!! ARCHITECTURE REBASELINE line and recorded in the landing's device note.
  • Any iOS model change, renamed device or other model change still needs LAND_ARCH_REBASELINE.
  • After a rebaseline, positions compare x86_64 with x86_64. ci-only never runs the arm64 lanes, and under ci the ABI guard above stops the driver instead.

Tests changed (reasons)

  • land.test.ts, LAND_REGEN mode test. It now uses parseLandModes, and ci-only is valid. The source checks follow the new code (parseLandModes(env), REGEN_ON !== 'local').
  • land.test.ts, prepared round JSON. A parsed Fatal now carries outage: false.
  • land-devices-ci.test.ts, wait limit. The wait-limit message now adds "(counted from its first job's start)". The existing expected substring is unchanged.
  • land-devices-ci.test.ts, stuck run. This test is unchanged. It now reaches "never started" through the queue wait.

New tests:

  • ci-only parsing: LAND_CI, the three modes, conflicts, LAND_CI_MAX_INFLIGHT.
  • No local fallback in ci-only. Every CiUnavailable catch and both CI-readiness checks throw CiOutage before any local run.
  • No lease, quiet-machine or priority use under LAND_CI=only.
  • runBatches outages while proving, building and bisecting, and from a builder. No PR is failed, the queued PRs are listed, and the status text is checked.
  • Queued vs never started, for the device lanes and the regen. This covers waiting past the start limit, the queue wait running out, a pending run with no jobs, and a run with nothing waiting.
  • Rebaseline, built from this tree's own device records in both ABIs:
    • It is accepted when every verdict is equal.
    • It is refused when a changed lane gains a failure or loses one.
    • It is never automatic for an iOS model change, a renamed device or a changed vectors device.
  • The driver wiring.

What passed

  • pnpm typecheck
  • pnpm vitest run on land.test.ts, land-devices-ci.test.ts, land-parallel.test.ts, land-supervisor.test.ts, merge-train.test.ts, floor-merge.test.ts, chrome-ports.test.ts, and both registry-claims tests: 9 files, 252 tests passed.

No network is used in the tests. No tolerance or check was loosened.

Not run: pnpm regen and the full pnpm test. Nothing here is a generator input.

🤖 Generated with Claude Code

…h pnpm ci:test-files to dispatch and read it over REST
…ems 1 and 4)

scripts/cloud-setup.sh, run by the project SessionStart hook only when CLAUDE_CODE_REMOTE=true, installs the Node every
workflow pins and the pnpm of packageManager, then pnpm install --frozen-lockfile and pnpm setup:git, which now also turns
rerere off. DRAGON_REQUIRE_NATIVE=1 turns a native run with a missing toolchain from blocked (owner tooling) into a failure
naming the tool, in runTarget and runHostLane, as CI's "No native run was blocked" step does.
…group; ci:test-files reads reports strictly and removes them
…then the merge drivers)

floor-merge.test.ts pins pnpm setup:git to exactly what the landing driver sets; rerere.enabled false joins both, so the
exact-equality check stays and covers the new line.
…_NATIVE=1, prove the Linux path in CI

Review of 892a687: the hook now has no matcher, so it reruns after compact, clear and fork (compaction drops CLAUDE_ENV_FILE
exports); in a cloud session the script writes export DRAGON_REQUIRE_NATIVE=1 to CLAUDE_ENV_FILE first (PM ruling) and fails
without that file; only the summary line reaches stdout; npm --prefix; curl --retry-connrefused. ci.yml's checks job runs on
ubuntu-24.04 and runs cloud-setup.sh twice before setup-node, checking Node, pnpm, the export, typecheck and no reinstall.
…oid ABI rebaseline

- LAND_DEVICES, LAND_TEST and LAND_REGEN accept ci-only; LAND_CI=only sets all three. Under ci-only
  GitHub Actions not running a step is a CiOutage: the driver stops, fails no PR, and every PR not
  landed stays queued (status: STOPPED BY A CI OUTAGE). All three ci-only skips the priority and
  quiet files; the leases are only on local paths, which ci-only never reaches.
- awaitOnCi tells a run queued for runners (jobs queued, or the run pending) from one that never
  started: queued runs are waited for up to LAND_CI_QUEUE_WAIT (default 3 h); the run's own wait
  counts from its first job's start.
- LAND_CI_MAX_INFLIGHT (default 2) caps the prepared CI regens a batch dispatches at once.
- R3: a CI device run whose only model changes are Android image ABI changes is an architecture
  rebaseline, logged loudly and recorded in the landing; each changed lane must keep the previous
  state and exactly its failures. Under LAND_DEVICES=ci a local fallback onto records of another
  ABI stops the driver instead of blaming the PR.
…re; a nonce finds the dispatched run; every test vitest collects must run; the setup test covers env, vitest command and blocked scan
…review)

A macOS job waiting for a runner while the Ubuntu jobs ran is queued, under LAND_CI_QUEUE_WAIT from
when it was first seen waiting; a running job has the step's wait from its own start; a run with
jobs done but not completed is waited for up to the queue wait. Under LAND_CI=only, the PR's own CI
never running or never finishing is a CiOutage. Unreadable previous device records in the ci-mode
fallback refusal stop the driver (Fatal) instead of blaming the PR.
Commands: pnpm regen; pnpm run parity:devices; pnpm regen
@thejackshelton
thejackshelton merged commit b13d66b into master Oct 8, 2026
5 checks passed
@thejackshelton
thejackshelton deleted the land-ci-only branch October 8, 2026 07:38
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.

1 participant