Skip to content

ci: add GitHub Actions workflow encoding the CLAUDE.md gates#169

Merged
tobert merged 3 commits into
mainfrom
ci/github-actions
Jul 13, 2026
Merged

ci: add GitHub Actions workflow encoding the CLAUDE.md gates#169
tobert merged 3 commits into
mainfrom
ci/github-actions

Conversation

@tobert

@tobert tobert commented Jul 13, 2026

Copy link
Copy Markdown
Owner

kaish has had no CI since the repo began — every gate in CLAUDE.md (tests, clippy,
the capability-feature sandbox build, the WASI target) ran only on contributor
machines. This PR adds the repo's first GitHub Actions workflow, modeled on
kaibo's ci.yml but encoding kaish's own gates. kaish releases to crates.io
rather than shipping binaries, and publishing stays deliberately manual (the
/release runbook), so there is no release workflow — ci.yml is the whole story.

Three parallel jobs, all with pinned-by-SHA actions (a tag can be moved, a digest
can't):

  • test + clippycargo test --all --locked, then
    cargo clippy --all --all-targets --locked -- -D warnings, then a tripwire that
    fails if any insta *.snap.new pending snapshot was committed (under CI insta
    fails-without-writing, so any .snap.new found was committed from a local run).
  • no-default-featurescargo test -p kaish-kernel --no-default-features --locked, the gate that catches OS-touching code landing outside its capability
    axis.
  • wasi buildcargo build -p kaish-wasi --target wasm32-wasip1 --locked, so
    embedders on that target don't discover breakage at release time.

Decisions along the way:

  • --locked everywhere: Cargo.lock is tracked, and CI drifting to newer deps
    than contributors test against is a silent-fallback class of surprise.
  • No cargo insta test --check job: cargo test --all already fails on snapshot
    mismatch under CI, and the .snap.new tripwire covers the committed-pending
    case — same coverage without installing cargo-insta or running the suite twice.
  • kaibo's TLS-invariant guard was dropped: it exists to keep kaibo's static musl
    builds honest, and kaish has no TLS or binary-release surface.
  • A kaibo (deepseek) review of the workflow found one real gap, fixed here:
    .gitignore had no *.snap.new entry, so the very mistake the CI tripwire
    catches could be staged by git add . locally. Belt added; CI stays suspenders.

All three legs were verified locally before encoding. This PR's own checks are the
live validation that the workflow runs on pull_request; a follow-up doc-sweep PR
(README badge + CI mentions in CLAUDE.md/README) validates the on-push-to-main leg
and the badge.

🤖 Generated with Claude Code

kaish had no CI — every gate (tests, clippy, the capability-feature sandbox
build, the WASI target) ran only on contributor machines, so nothing stopped
a red suite or a new lint from landing via a PR that skipped the runbook.

Modeled on kaibo's ci.yml (pinned-SHA actions, minimal permissions, per-ref
concurrency) but encoding kaish's own gates as three parallel jobs:
test+clippy (-D warnings, --all-targets), kernel no-default-features tests,
and the wasm32-wasip1 build. kaish releases to crates.io rather than
binaries and publishing stays manual (/release), so there is no release
workflow; kaibo's TLS-invariant guard was dropped as kaibo-specific.

Instead of a second cargo-insta run, a tripwire step fails on any committed
*.snap.new — under CI insta fails tests without writing files, so any found
were committed locally. A kaibo review caught the matching gap on the local
side: .gitignore had no *.snap.new entry, added here.

All three legs verified locally (test suite, no-default-features tests,
wasi build all green) before encoding.

Co-Authored-By: Claude Fable 5 <[email protected]>
@tobert

tobert commented Jul 13, 2026

Copy link
Copy Markdown
Owner Author

First live run: two failures, both real

The first CI run on this PR failed two of three jobs — and both failures were
pre-existing repo bugs the workflow correctly exposed, not workflow bugs:

test + clippy: non_interactive_stdin_is_dev_null flake. The test's comment
says "Use /bin/readlink to bypass the builtin," but the code ran bare readlink
which resolves to kaish's builtin readlink. The builtin resolves
/proc/self/fd/0 of the test process, not of a spawned child, so the test
only ever passed in environments where the test runner itself had
stdin=/dev/null. GitHub's runner gives the step a pipe → pipe:[17586]
failure. Reproduced locally with echo poke | cargo test … (red), fixed by
making the code match its comment (/bin/readlink), re-ran with piped stdin
(green). The ignored interactive_stdin_is_not_dev_null got the same fix for
intent-consistency.

no-default-features: never actually compiled. KernelConfig::repl() is
#[cfg(feature = "localfs")] and ~25 integration-test files call it —
cargo test -p kaish-kernel --no-default-features has never built. My earlier
"local verification" of this leg piped cargo through tail, so the exit code I
checked was tail's, not cargo's (mea culpa; rerun properly it exits 101). The
documented invariant is "sandbox-mode compiles", so the leg is now
cargo check --no-default-features — and upgrading it to cargo test by
feature-gating the test crates is filed as #170.

tobert and others added 2 commits July 13, 2026 17:07
The first run of the new workflow failed two of three jobs; both were
pre-existing repo bugs, not workflow bugs.

The stdin test's comment said "/bin/readlink to bypass the builtin" but the
code ran bare `readlink` — the kaish builtin, which resolves the *test
process's* fd/0, not a spawned child's. It only passed where the test runner
itself had stdin=/dev/null; GitHub's runner hands the step a pipe.
Reproduced red locally with piped stdin, fixed by making the code match the
comment, verified green the same way. The ignored interactive twin got the
same treatment.

The no-default-features leg was written as `cargo test`, which has never
compiled: KernelConfig::repl() is #[cfg(feature = "localfs")] and ~25
integration-test files call it. The earlier local "verification" piped cargo
through tail and read tail's exit code — properly rerun it exits 101. The
documented invariant is "sandbox-mode compiles", so the leg is now
`cargo check`; upgrading it to `cargo test` via feature-gated test crates is
GH #170.

Co-Authored-By: Claude Fable 5 <[email protected]>
Third CI finding on PR #169: clippy passes locally on stable 1.96 but the
runner's stable is 1.97, whose question_mark lint now catches a
strip_prefix match that an early-return `?` expresses directly. Toolchain
drift between contributor machines and CI is precisely what the workflow
is for — the fix is the suggested `?`, not a pinned toolchain.

Co-Authored-By: Claude Fable 5 <[email protected]>
@tobert tobert merged commit bcbeb11 into main Jul 13, 2026
3 checks passed
tobert added a commit that referenced this pull request Jul 13, 2026
Follow-up to #169: the workflow exists; this wires it into the docs
contributors actually read, and serves as the final validation pass —
this PR itself exercises the `pull_request` trigger on a fresh branch,
and its merge will light the README badge from the `push`-to-main leg.

- **README**: ci badge + crates.io badge (kaish-kernel, the flagship
embeddable crate). The `Status:` line stops hand-maintaining a version
number — it had already drifted (said 0.11, we're on 0.12); the badge
carries it now. "Building from Source" shows the clippy gate and points
at `ci.yml`; "Contributing" notes CI must be green and warns that
runners track current stable Rust, so CI clippy may know lints your
local toolchain doesn't (this bit us live: 1.97's `question_mark` failed
a run 1.96 passed).
- **CLAUDE.md**: Build Commands documents what CI runs, the
keep-`ci.yml`-in-sync rule, and the fix-the-code-don't-pin-the-toolchain
stance; the pre-commit gate list notes CI now enforces it.
- **docs/devlog.md**: the full arc of #169 — first run failed two of
three jobs, every failure a pre-existing repo bug (builtin-shadowed
`readlink` stdin test; a no-default-features test leg that never
compiled → #170; toolchain-drift clippy).

🤖 Generated with [Claude Code](https://claude.com/claude-code)
pull Bot pushed a commit to indielab/kaish that referenced this pull request Jul 13, 2026
Follow-up to tobert#169 (the CI workflow itself): wire the new CI into the docs
that tell people how to work here.

README gains the ci badge and a crates.io badge for kaish-kernel, and the
"Status:" line stops hand-maintaining a version number that had already
drifted (said 0.11, we're on 0.12) — the badge carries it now. Building
from Source shows the clippy gate and names ci.yml as the enforcement
point; Contributing notes CI must be green and that runners track current
stable Rust, so CI clippy can know lints a local toolchain doesn't
(discovered live when 1.97's question_mark lint failed a run that 1.96
passed).

CLAUDE.md's Build Commands section now documents what CI runs and the
keep-ci.yml-in-sync rule; the pre-commit gate list notes CI enforces it.
Devlog entry covers the whole arc: first run failed two of three jobs,
every failure a pre-existing repo bug (builtin-shadowed readlink stdin
test, a no-default-features test leg that never compiled → GH tobert#170,
toolchain-drift clippy).

Co-Authored-By: Claude Fable 5 <[email protected]>
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