Files
max-sixty__worktrunk/.github/workflows/nightly.yaml
T
Worktrunk Bot f2ae1c5fb5 chore(ci): weekly renovation 2026-08-23 (#3880)
## Summary

Weekly CI renovation. Rust stable moved to 1.98.0 on 2026-08-20, so MSRV
and the development toolchain go to 1.97 (latest stable − 1); one
cargo-install pin had drifted. Everything else is already current.

- **MSRV + toolchain: 1.96 → 1.97** — `Cargo.toml`,
`tests/helpers/wt-perf/Cargo.toml`, `rust-toolchain.toml` (`1.97.0`),
plus the `flake.lock` refresh the channel bump requires.
- **`cargo-llvm-cov`: 0.8.7 → 0.9.0** — `coverage.yaml`. MSRV 1.87, well
under the pinned toolchain.

Toolchain compatibility for every other pinned crate against 1.97.0:
`cargo-affected` 0.4.0 (1.94), `cargo-insta` 1.48.0 (1.66.0),
`cargo-nextest` 0.9.143 (1.91), `cargo-llvm-cov` 0.9.0 (1.87),
`cargo-msrv` 0.19.3 (1.91.1), `lychee` 0.24.2 (1.88.0), `worktrunk`
0.74.0 (1.96), `cargo-udeps` 0.1.61 (unspecified). All build under
1.97.0.

- **Pinned nightly: `nightly-2026-03-01` → `nightly-2026-08-01`** —
`nightly.yaml` ×2. Not drift: the MSRV bump *broke* these two jobs, see
below.
- **The skill's `flake.lock` recipe** — the in-session Nix install it
pointed at can never run. The setup step that fixes that lives in #3891,
not here; see below.

The remaining commits are fallout from the same sweep: `cargo-llvm-cov`
0.9.0 changes the shape of the `--show-missing-lines` column
`tests/CLAUDE.md` sends you to during a codecov investigation, and the
weekly statusline cache-check has been reporting two duplicates that are
artifacts of its own `-vv` flag.

## The MSRV bump broke two nightly jobs; that is fixed here

`check-unused-dependencies` and `minimal-versions` each pin their own
toolchain, and `nightly-2026-03-01` is `1.96.0-nightly` — so raising
`rust-version` to 1.97 made cargo refuse the workspace outright, before
either job ran its actual check:

```
error: rustc 1.96.0-nightly is not supported by the following packages:
  worktrunk@0.74.0 requires rustc 1.97
```

Both are now on `nightly-2026-08-01` (`1.99.0-nightly`), which clears
the new MSRV with headroom rather than landing on it. Reproduced locally
in both directions: `cargo +nightly-2026-03-01 check` fails with the
error above, `cargo +nightly-2026-08-01 check` passes. The coupling is
easy to miss because neither failure mentions udeps or minimal versions,
so both lines now say so, and the weekly MSRV file table gained a row
for the pins. The rule on that row is a date, not a version: bump only
when the pinned date is more than three months old. Nightly runs two
channels ahead of stable and MSRV tracks stable − 1, so a pin that
recent is still a release above the new MSRV — which makes staleness
checkable from the pinned date alone, where reading it off the rustc
version means fetching that day's `channel-rust-nightly.toml`.
`nightly-2026-03-01` was five and a half months old, which is exactly
what the rule catches.

## The `flake.lock` bump was computed by hand — and the recipe that
forced that is fixed here

`rust-toolchain.toml` moving to `1.97.0` forces a lock refresh: the
locked `rust-overlay` (`4a408e1f`, 2026-06-02) carries stable manifests
only up to `1.96.0`, so `nix flake check` cannot resolve the new
channel. `rust-overlay` is bumped to `f60c1b57` (2026-08-23), which has
`1.97.0.nix`. `nixpkgs` and `crane` are left alone — only `rust-overlay`
has to know about the new channel, and a full `nix flake update` is a
wider change than this PR needs.

This session could not run `nix flake update`: the weekly runner has no
Nix, and the agent runs as a sandbox user with no sudo, so the installer
the skill pointed at stops at `sudo: a password is required`. The lock
entry was therefore computed directly from the NAR serialisation, and
the method was validated first by recomputing the **existing**
`rust-overlay` entry from its own revision and getting
`sha256-7mDa7OBAaf7MU6ZovT9ENfD62kH911SsSazBb4KTDF0=` back byte-for-byte
— the value already in `flake.lock`. The same script then produced the
new entry.

That should not have been the answer, and it no longer is. #3891 has
merged (`2960bfb`), so `tend-setup` installs Nix and next week's refresh
runs `nix flake update rust-overlay` for real. This branch briefly
carried its own `cachix/install-nix-action@v31` step in the same file;
`adbeb76` dropped it in favour of #3891's, which asserts the multi-user
install branch directly and doesn't couple to `github.workflow ==
'tend-weekly'`, a gate that stops firing if the generated workflow is
renamed. `.github/actions/tend-setup/action.yaml` on this branch is
byte-identical to `main`.

What stays here is the skill side: the in-session install recipe and the
NAR-serialisation fallback are both deleted, because the Determinate
installer they rest on cannot run in the sandbox. Both PRs rewrote that
section and converged on nearly the same wording, so they merged clean
while both were open — but #3891's squash collapsed its five commits
into one, and its last two had moved the "if `nix` isn't on the PATH"
line and folded the flake-input rationale into the code block. That
turned the section into the merge's one conflict, resolved by taking
`main`'s wording wholesale; the section is now byte-identical to
`main`'s, and this branch's own additions to that file are untouched by
it.

That is a strong check but not the real one. `nightly`'s `nix-flake` job
runs `nix flake check` and fires on this PR (the paths filter matches
`rust-toolchain.toml`, `Cargo.toml`, and `flake.lock`), so it is the
gate that actually proves the lock evaluates. **It has now run on this
head and passed**, so the computed lock resolves the 1.97.0 channel and
the flake's checks build under it. Flagging the provenance anyway, since
a hand-written lock entry deserves a second look on review.

<details><summary>How the hash was computed</summary>

Nix serialises the unpacked flake input as a NAR and reports SHA-256 of
that stream in SRI form. The format is length-prefixed strings padded to
8 bytes, with directory entries in bytewise-sorted order; a ~90-line
Python script reproduces it from the GitHub tarball with the top-level
`<repo>-<rev>` directory stripped, which is the same tree Nix hashes.

```
rust-overlay 4a408e1fc99ad517b4cb402fd0de7464f40c05e1 (currently locked)
  computed  sha256-7mDa7OBAaf7MU6ZovT9ENfD62kH911SsSazBb4KTDF0=
  in lock   sha256-7mDa7OBAaf7MU6ZovT9ENfD62kH911SsSazBb4KTDF0=   ✓

rust-overlay f60c1b57ff805a46b5175c76fc981fb4f81efbcc (new)
  computed  sha256-r4LDUF+zmJnkftvCVkCrUhSJazsf6EVJF+V2l4/MYbI=
  lastModified 1787454509 (committer date 2026-08-23T03:08:29Z)
```

</details>

## Already up to date

- `cargo-affected` 0.4.0, `cargo-insta` 1.48.0, `cargo-nextest` 0.9.143,
`cargo-msrv` 0.19.3, `cargo-udeps` 0.1.61, `lychee` 0.24.2, `worktrunk`
0.74.0 (matches the newest crates.io release)
- Nushell 0.115.0 — all four call sites, and `dev/codex.sh`
- Codex Cloud pins in `dev/codex.sh`: pre-commit 4.6.2, cargo-insta
1.48.0, cargo-nextest 0.9.143, PowerShell 7.6.5; `setup-web`'s own
Nushell and PowerShell pins agree
- Runner images `ubuntu-24.04` and `windows-2022` (the latter
deliberately pinned — actions/runner-images#12677)

## Found, not bumped: `macos-15` is no longer what `macos-latest`
resolves to

`ci.yaml`'s header comment says `ubuntu-24.04, macos-15` are "pinned to
current -latest equivalents". That is still true of Ubuntu but no longer
of macOS: `macos-latest` now resolves to **macOS 26 arm64**, and
`macos-15` has become the older of the two GA images — which, under the
runner-images deprecation policy ("deprecation of the oldest image label
begins once the newest OS image label has been released to GA"), is the
one that starts aging out next. `macos-14` already carries the
deprecated badge.

Not bumped here, because it is an OS upgrade rather than a version pin,
and this repo drives PTY and shell-integration snapshots on macOS that a
new image and Xcode can move. There is also nothing this session can do
to check it — the only macOS available to it is the `test (macos)` job
on a PR, and a red macOS matrix would have blocked the MSRV bump
alongside it. Flagging rather than guessing at the timing: whether to
take macOS 26 now or wait for the deprecation announcement is a call
about this repo's snapshot surface. Happy to open it as its own PR if
you want the signal.

## CI

Fully green on `7940a97`, the first merge of `main`: all 33 non-skipped
checks, including `msrv`, all three `test` legs, `full-tests` on every
platform, `nix-flake`, `minimal-versions`, `check-unused-dependencies`,
`check-docs`, `link-check`, `feature-powerset`, and `codecov/patch`.
That head is where the hand-written lock entry and the nightly pins were
proven against the current `main`.

`main` has since moved four commits (#3886, #3893, #3895, #3897) and is
merged in again at `d8d1553`, alongside the nightly-pin rule above.
#3895 touches `nightly.yaml`, so the pins were re-checked after that
merge: still exactly two sites, `minimal-versions` and
`check-unused-dependencies`, both on `nightly-2026-08-01`. Locally
`pre-commit run --all-files` and `test_docs_are_in_sync` pass on this
head.

## Weekly checks that produced no change

- **LLM model pins** — no drift. `gpt-5.6-luna` is still how OpenAI's
models page positions the "fast and affordable … lowest cost in the
family" 5.6 variant; `gpt-5.3-codex-spark` is a Pro-only research
preview, not a default. `haiku` / `claude-haiku-4.5` is still the
smallest current Anthropic model.
- **Statusline cache-check** — clean. `command_count` was 27, which
includes the three calls the `-vv` diagnostic collector makes after the
render, so the render itself is ~24 against a ~29 baseline measured
before the recipe used `-vv`. The two duplicates the report flagged (`gh
--version`, `git worktree list --porcelain`) are that same collector,
not the render. Both the baseline arithmetic and the trap are now
written down in the skill.
- **Agent app integration surfaces** — nothing that changes what
Worktrunk consumes. Claude Code 2.1.239 fixed `metadata.pluginRoot`
resolution, but `.claude-plugin/marketplace.json` names an explicit
`./plugins/worktrunk` source rather than a bare name, so it is
unaffected; the `WorktreeCreate`/`WorktreeRemove` hooks and the
statusline stdin schema are unchanged (`workspace.git_worktree` and
`workspace.repo` exist but predate this window by several hundred
releases). Codex, Gemini CLI, and OpenCode shipped only routine
releases.
- **README month** — already "August 2026".
- **Dependency PRs** — none open.

<details><summary>Verification</summary>

- `rustup` resolves the new channel: `cargo 1.97.0 (c980f4866
2026-06-30)`, `rustc 1.97.0 (2d8144b78 2026-07-07)`, `clippy 0.1.97`.
- `cargo clippy --all-targets --all-features -- -D warnings` passes on
1.97.0 — the check that matters most for a toolchain bump, since it
compiles every target and feature under the new rustc and holds the new
clippy to zero warnings. Doctests passed too.
- Unit tests pass on 1.97.0: 979 + 11 + 1, zero failures.
- Integration tests: 2000 passed, 1 failed —
`test_copy_ignored_preserves_file_executable_permissions`, which expects
`0644` and got `0664`. That is this sandbox's `umask 0002`, not the
toolchain; the `test (linux|macos|windows)` matrix is the real gate.
- `cargo run -- hook pre-merge --yes` is not a clean local signal on
this runner: it ends on `pre-commit: exit status: 127` because
pre-commit isn't installed in the tend sandbox, unrelated to the bump.
- `cargo msrv verify` was not run locally (it rebuilds the graph per
candidate); `ci.yaml`'s `msrv` job covers it.
- Every version above was read from its upstream source of truth: `cargo
info` / crates.io for the cargo tools,
`static.rust-lang.org/dist/channel-rust-stable.toml` for Rust stable
(1.98.0, 2026-08-20), the `nushell/nushell` and `PowerShell/PowerShell`
release APIs, PyPI for pre-commit, `actions/runner-images` README for
the image labels, and the vendors' own model pages for the LLM pins.
- `rust-overlay` at the currently-locked revision was confirmed to stop
at `1.96.0.nix`, and at the new revision to contain `1.97.0.nix` — this
is why the lock has to move at all.

</details>



> _This was written by Claude Code on behalf of max-sixty_

---------

Co-authored-by: worktrunk-bot <254187624+worktrunk-bot@users.noreply.github.com>
Co-authored-by: Maximilian Roos <m@maxroos.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-24 13:54:54 -07:00

485 lines
19 KiB
YAML

name: nightly
# Slower checks that aren't worth running on every PR but should run before a
# release. Jobs:
# - feature-powerset: feature-flag unification, check + test suite (motivated by #2442)
# - full-tests: full nextest suite on the standard OS matrix and Git 2.43.0 —
# the cargo-affected safety net (see the job comment and ci.yaml's affected block)
# - release-target: PTY+shell suite on the release triples ci.yaml doesn't cover
# - check-unused-dependencies: cargo-udeps on nightly toolchain
# - minimal-versions: cargo check against minimum-version dep resolution
# - nix-flake: nix flake check (packaging-environment bugs, motivated by #2624)
# - crate-build: build the crates.io archive with no `.git` (faithful #3123 repro)
# - link-check: lychee over tracked .md/.txt files (external-link volatility)
#
# Runs daily on cron, on demand via workflow_dispatch, on pushes to main that
# touch dependency, toolchain, or nix packaging files, and on PRs that either
# (a) touch the same files or (b) carry the `nightly` label. The
# label is the iteration knob for fixes targeting nightly-only failures
# (e.g. nix-flake's sandbox suite); without it, contributors had to wait for
# the cron run or `gh workflow run` manually. The cron still catches drift
# that's not commit-correlated (registry updates, transitive resolution).
#
# Runner versions pinned; see ci.yaml header comment for rationale.
on:
schedule:
# Run at 5:37 UTC every day. Off-peak minute (avoid :00 to be a good
# citizen w.r.t. GitHub's cron scheduler).
- cron: '37 5 * * *'
workflow_dispatch:
push:
branches: [main]
paths:
- '**/Cargo.toml'
- 'Cargo.lock'
- 'rust-toolchain.toml'
- 'flake.nix'
- 'flake.lock'
- 'nix/**'
- '.github/workflows/nightly.yaml'
pull_request:
branches: [main]
# `labeled` fires when a label is added (so the `nightly` label can
# trigger a run mid-PR); `synchronize` re-runs on subsequent pushes.
# The `gate` job below decides whether to actually run, ORing the
# label against a Cargo-paths diff check.
types: [opened, synchronize, reopened, labeled]
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
env:
CARGO_TERM_COLOR: always
CARGO_INCREMENTAL: 0
RUSTFLAGS: -C debuginfo=0
jobs:
gate:
# Decides whether to run on PR events. Non-PR events (cron,
# workflow_dispatch, push) pass through unconditionally. PR events
# run when either the `nightly` label is attached OR the diff touches
# one of the dependency/toolchain/nix files. The decision is exposed
# via `outputs.run`; downstream jobs gate on it.
#
# `dorny/paths-filter` works against the GitHub API for PR events, so
# no checkout step is needed.
runs-on: ubuntu-24.04
permissions:
pull-requests: read
outputs:
run: ${{ steps.decide.outputs.run }}
steps:
- uses: dorny/paths-filter@v4
if: github.event_name == 'pull_request'
id: changes
with:
filters: |
nightly:
- '**/Cargo.toml'
- 'Cargo.lock'
- 'rust-toolchain.toml'
- 'flake.nix'
- 'flake.lock'
- 'nix/**'
- '.github/workflows/nightly.yaml'
- id: decide
run: |
echo "run=${{ github.event_name != 'pull_request' ||
contains(github.event.pull_request.labels.*.name, 'nightly') ||
steps.changes.outputs.nightly == 'true' }}" >> "$GITHUB_OUTPUT"
feature-powerset:
# Every combination of cli/syntax-highlighting/shell-integration-tests/
# git-wt should compile. Catches regressions in feature gating — including
# the v0.45.0 case where lib code used a `cli`-gated dependency
# unconditionally.
#
# Workspace members must use `default-features = false` when depending on
# worktrunk, or feature unification will mask gating bugs by silently
# enabling `cli` in the lib build (see tests/helpers/wt-perf/Cargo.toml).
#
# The test step runs the test suite per combination — the check step
# strips dev-deps, so `#[cfg(test)]` code is invisible to it, and a test
# importing a feature-gated symbol without its own gate, or a snapshot
# baking feature-dependent output, only surfaces when tests are built and
# run per combo. The integration suite declares
# `required-features = ["cli", "syntax-highlighting"]` (Cargo.toml) because
# it execs the `wt` binary and snapshots its highlighted output, so cargo
# runs it only on combinations that satisfy both and runs lib + doc tests
# everywhere.
needs: gate
if: needs.gate.outputs.run == 'true'
runs-on: ubuntu-24.04
steps:
- name: 📂 Checkout code
uses: actions/checkout@v7
- name: 💰 Cache
uses: Swatinem/rust-cache@v2
with:
# Restore the shared `test` cache (registry + deps), never save.
# Nightly-only jobs riding main's cache aren't worth their own entry
# against the 10 GB repo cap.
prefix-key: v1-rust
shared-key: shared
cache-bin: "false"
save-if: false
- name: Install cargo-hack
uses: taiki-e/install-action@v2.86.5
with:
tool: cargo-hack
# The `shell-integration-tests` combinations run PTY tests that spawn real
# zsh/fish and probe nushell; match the test matrix's shell setup
# (.github/actions/test-setup) so those combinations run, not just compile.
- name: Install shells (zsh, fish)
run: sudo apt-get update && sudo apt-get install -y zsh fish
- name: Install nushell
uses: hustcer/setup-nu@v3
with:
version: '0.115.0'
- run: cargo hack check --feature-powerset --no-dev-deps
- run: cargo hack test --feature-powerset
full-tests:
# The full nextest suite on the standard linux/macos/windows matrix —
# mirrors ci.yaml's PR `test` job. This is the safety net for the gaps
# cargo-affected can't cover: tests it under-selects, plus the non-Rust /
# build inputs it can't trace (`include_str!`, templates, build.rs,
# rust-toolchain.toml, proc-macros). It also hosts the Linux
# `--unreferenced reject` orphan check, which is intrinsically full-suite.
# It lives in `nightly`, NOT on push-to-main, on purpose: a failure here
# means affected missed something, and that must not redden main — nightly
# failures are non-blocking and Tend-fixable. Complements feature-powerset
# (Linux, feature combos) and release-target (cross triples) by covering
# the standard 3-OS matrix that neither runs. A fourth row runs the same
# job with the minimum supported Git. At the affected-only flip, ci.yaml's
# `test` job is deleted and this becomes the sole full run.
needs: gate
if: needs.gate.outputs.run == 'true'
strategy:
fail-fast: false
matrix:
include:
- os: ubuntu-24.04
name: linux
- os: ubuntu-24.04
name: git-2.43.0
git: '2.43.0'
- os: macos-15
name: macos
- os: windows-2022
name: windows
runs-on: ${{ matrix.os }}
steps:
- name: 📂 Checkout code
uses: actions/checkout@v7
- uses: ./.github/actions/test-setup
- name: Install Git 2.43.0
if: matrix.git == '2.43.0'
shell: bash
run: |
sudo apt-get update
git_package_version="$(apt-cache madison git | awk '$3 ~ /^1:2[.]43[.]0-/ { print $3; exit }')"
if [ -z "$git_package_version" ]; then
echo "::error::Ubuntu package sources do not provide Git 2.43.0"
exit 1
fi
sudo apt-get install -y --allow-downgrades \
"git=$git_package_version" "git-man=$git_package_version"
echo "/usr/bin" >> "$GITHUB_PATH"
- name: Verify minimum Git
if: matrix.git == '2.43.0'
run: test "$(git --version)" = "git version 2.43.0"
- name: Install wt
uses: baptiste0928/cargo-install@v3
with:
crate: worktrunk
version: "=0.74.0"
- name: "Use fast D: drive for temp files (Windows)"
if: runner.os == 'Windows'
shell: pwsh
run: |
New-Item -ItemType Directory -Force -Path "D:\tmp" | Out-Null
echo "TEMP=D:\tmp" >> $env:GITHUB_ENV
echo "TMP=D:\tmp" >> $env:GITHUB_ENV
- name: 🧪 Full test suite
run: wt hook pre-merge --yes insta
- name: 🧹 Verify clean working tree
shell: bash
run: |
if [ -n "$(git status --porcelain)" ]; then
echo "::error::tests left files behind in the working tree:"
git status --porcelain
exit 1
fi
release-target:
# Build and run the test suite on the release triples that ci.yaml's
# `test` matrix doesn't cover — `dist-workspace.toml` ships musl Linux
# (x86_64 + arm64) and Intel macOS, but the test matrix is glibc Linux,
# arm64 macOS, and Windows. Without this, a regression on those targets
# first surfaces at release-tag time, which blocks the release.
#
# Runs the integration suite (default features, no
# `shell-integration-tests`) — covers file IO, command spawning, and
# output rendering on the cross-target triple, which is where
# musl-vs-glibc and intel-vs-arm divergence shows up. Shell-integration
# PTY tests are intentionally off because they read $SHELL from the
# runner env, which resolves differently on `ubuntu-24.04-arm` and
# masks musl/arm signal with environment noise.
needs: gate
if: needs.gate.outputs.run == 'true'
strategy:
fail-fast: false
matrix:
include:
- target: x86_64-unknown-linux-musl
runner: ubuntu-24.04
install: musl-tools
- target: aarch64-unknown-linux-musl
runner: ubuntu-24.04-arm
install: musl-tools
- target: x86_64-apple-darwin
runner: macos-15-intel
name: release-target (${{ matrix.target }})
runs-on: ${{ matrix.runner }}
steps:
- name: 📂 Checkout code
uses: actions/checkout@v7
- name: Install musl-tools
if: matrix.install == 'musl-tools'
run: sudo apt-get update && sudo apt-get install -y musl-tools
- uses: ./.github/actions/test-setup
with:
# Cross-compiles musl/Intel targets the shared cache never builds, so
# it restore-misses anyway — and we don't want these one-off release
# triples writing their own caches against the 10 GB cap.
save-cache: "false"
- name: Add target
run: rustup target add ${{ matrix.target }}
- name: 🧪 Tests
run: cargo nextest run --target ${{ matrix.target }}
check-unused-dependencies:
# Moved from ci.yaml — `cargo udeps` requires nightly toolchain anyway,
# so it's already in the "nightly concern" bucket; rarely flips between
# PRs touching deps.
needs: gate
if: needs.gate.outputs.run == 'true'
runs-on: ubuntu-24.04
steps:
- name: 📂 Checkout code
uses: actions/checkout@v7
# cargo-udeps requires nightly. Keep at or above `rust-version` in
# `Cargo.toml`; see the note on `minimal-versions`' identical pin.
- run: rustup override set nightly-2026-08-01
- name: 💰 Cache
uses: Swatinem/rust-cache@v2
with:
# Never save. Runs on the nightly toolchain, whose rustc hash can't
# match the shared (stable) cache, so a per-job cache would only ever
# serve the next nightly — near-zero hit under the 10 GB cap. Cold
# builds here are absorbed by the nightly cadence.
cache-bin: "false"
save-if: false
- uses: baptiste0928/cargo-install@v3
with:
crate: cargo-udeps
version: "=0.1.61"
- uses: clechasseur/rs-cargo@v5.0.8
with:
command: udeps
args: --all-targets
minimal-versions:
# Verify `Cargo.toml` constraints aren't under-specified — library
# consumers (the lib/CLI cleave is real; `feature-check` in ci.yaml
# exists for the same downstream-protection reason) can resolve to a
# lower compatible version that doesn't actually compile if our manifest
# under-specifies.
needs: gate
if: needs.gate.outputs.run == 'true'
runs-on: ubuntu-24.04
steps:
- name: 📂 Checkout code
uses: actions/checkout@v7
# Pinned so `-Z direct-minimal-versions` resolves reproducibly. Must stay
# at or above `rust-version` in `Cargo.toml`: cargo refuses a workspace
# whose MSRV exceeds the toolchain, so a stale pin fails the job outright
# rather than reporting on dependency floors. Bumped by the weekly MSRV
# pass, with headroom so that isn't every week.
- run: rustup override set nightly-2026-08-01
- name: 💰 Cache
uses: Swatinem/rust-cache@v2
with:
# Never save — nightly toolchain (can't match the shared stable cache)
# plus a minimized lockfile that's unique to this run, so a saved cache
# would never be reused.
cache-bin: "false"
save-if: false
- name: Resolve to minimum versions
# `direct`, not full `-Z minimal-versions`: minimize only worktrunk's own
# direct deps and let transitive crates resolve normally. Full minimization
# also walks skim 4.8's TUI/image stack (ansi-to-tui, ratatui's
# `instability` macro, color-eyre, ratatui-image -> image/avif -> num-* and
# bitvec), whose crates under-declare their floors and don't compile at the
# picked versions — upstream brokenness, not ours, that we'd have to pin
# around. Direct minimization confines the check to floors we actually own;
# they're raised in the manifests to the minimums the tree builds against.
run: cargo update -Z direct-minimal-versions
- name: cargo check
run: cargo check --workspace --all-targets
crate-build:
# Faithful end-to-end guard for #3123: build the crates.io *source archive*
# in a directory with no ancestor `.git` and confirm `wt` both compiles and
# reports the cargo version (the `option_env!("VERGEN_GIT_DESCRIBE")`
# fallback). ci.yaml's fast `vergen_env_vars_are_read_optionally` guard
# catches the mechanism (`env!` vs `option_env!`) on every PR by scanning
# source; this reproduces the actual `cargo install` condition — a full
# from-scratch build (~minutes), which is why it's `#[ignore]`d and run here
# via `--ignored`. A regression that an in-tree build can't see (git
# describe ascends to the outer repo) surfaces only on this no-git build.
needs: gate
if: needs.gate.outputs.run == 'true'
runs-on: ubuntu-24.04
steps:
- name: 📂 Checkout code
uses: actions/checkout@v7
- name: 💰 Cache
uses: Swatinem/rust-cache@v2
with:
# Restore the shared `test` cache (registry + deps), never save.
prefix-key: v1-rust
shared-key: shared
cache-bin: "false"
save-if: false
- name: 🧪 Build crates.io archive without .git
run: cargo test --test integration crate_io_archive_builds_and_versions_without_git -- --ignored
link-check:
# The shared docs action verifies and builds the site before the pre-commit
# `lychee-system` hook checks every tracked .md/.txt file. Lychee remains in
# the nightly workflow rather than required PR CI because external link
# health depends on 429s, bot-blocking, and transient outages. A scheduled
# docs or link failure opens the nightly-failure issue.
needs: gate
if: needs.gate.outputs.run == 'true'
runs-on: ubuntu-24.04
env:
# Authenticate lychee requests to GitHub to avoid rate limiting
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
steps:
- name: 📂 Checkout code
uses: actions/checkout@v7
- name: 🕷️ Check and build docs
uses: ./.github/actions/docs-build
- name: Install lychee
uses: baptiste0928/cargo-install@v3
with:
crate: lychee
version: "=0.24.2"
- name: 🔗 Check links
# Through pre-commit so file selection (the `files` regex, symlink
# exclusion) has one definition; the manual stage keeps it out of the
# PR `lint` job and local `pre-commit run --all-files`.
run: pipx run pre-commit run lychee-system --all-files --hook-stage manual
nix-flake:
# Build and test under the nix sandbox so packaging-environment bugs
# surface before a release / nixpkgs maintainer hits them. See #2624 for
# the canonical example: a unit test that depends on the process CWD
# being inside a git repo, which fails in the sandbox where source is
# extracted from a tarball without `.git`.
#
# Runs `nix flake check`, which exercises every check defined in
# flake.nix — in particular `worktrunk-tests`, which runs `cargo test`
# with default features (lib + bins + integration + doctests; the
# `shell-integration-tests` feature is intentionally off). Cold builds
# take ~10-15 min without a binary cache; nightly cadence absorbs it.
needs: gate
if: needs.gate.outputs.run == 'true'
runs-on: ubuntu-24.04
steps:
- name: 📂 Checkout code
uses: actions/checkout@v7
- name: Install Nix
uses: cachix/install-nix-action@v31
with:
extra_nix_config: |
experimental-features = nix-command flakes
access-tokens = github.com=${{ secrets.GITHUB_TOKEN }}
- name: nix flake check
run: nix flake check --print-build-logs --keep-going
create-issue-on-nightly-failure:
needs:
- feature-powerset
- full-tests
- release-target
- check-unused-dependencies
- minimal-versions
- nix-flake
- crate-build
- link-check
if: always() && contains(needs.*.result, 'failure') && github.repository_owner == 'max-sixty' && github.event_name == 'schedule'
runs-on: ubuntu-24.04
environment:
name: tend
# A secret scope, not a deploy target — see .github/CLAUDE.md. The cron
# gate above keeps this off the workflow's pull_request and
# workflow_dispatch triggers, whose refs the `tend` policy can refuse;
# its push trigger is `main`-only, which the policy already admits.
deployment: false
permissions:
contents: read
issues: write
steps:
- name: 📂 Checkout code
uses: actions/checkout@v7
- uses: JasonEtco/create-an-issue@v2
env:
# Use TEND_BOT_TOKEN for a consistent bot identity (per
# .github/CLAUDE.md) and so any future issue-triage automation can
# cascade off issue creation — events from the default GITHUB_TOKEN
# don't trigger other workflows.
GITHUB_TOKEN: ${{ secrets.TEND_BOT_TOKEN }}
LINK: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
with:
filename: .github/nightly-failure.md
update_existing: true
search_existing: open