Skip to content
This repository was archived by the owner on Jul 24, 2026. It is now read-only.

feat(nix): self-contained flake + nix flake check as CI - #87

Merged
schickling merged 2 commits into
schickling-assistant/2026-07-20-completionsfrom
schickling-assistant/2026-07-20-nix-flake
Jul 20, 2026
Merged

feat(nix): self-contained flake + nix flake check as CI#87
schickling merged 2 commits into
schickling-assistant/2026-07-20-completionsfrom
schickling-assistant/2026-07-20-nix-flake

Conversation

@schickling-assistant

@schickling-assistant schickling-assistant commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

What

A self-contained flake.nix, and convoy's first CI — which is the same thing: nix flake check is the gate.

Stacked on #85. This PR's base is the completions branch, because checks.completions runs convoy completions <shell> on the built binary. Merge #85 first.

Also blocked on compoundingtech/smalltalk#104. smalltalk's flake does not exist on its main yet, so the smalltalk input points at that PR's branch, with a # TODO: to repoint once it merges. That is the other reason this cannot merge yet.

The flake

Thin and self-contained. Inputs: nixpkgs, flake-utils, pty and smalltalk from their own flakes (both with nixpkgs followed, and smalltalk.inputs.pty followed so there is exactly one pty in the closure). No first-party tooling deps — it builds standalone from a bare checkout.

  • Builds from self, so it packages the revision it ships with rather than a pinned fetchFromGitHub.
  • Version from package.json (0.2.0-ts.0) — a release bump needs no edit here.
  • Sibling links are declarative: an npm-name -> store-path attrset, rendered into node_modules by a shared linkSiblings helper, instead of ad-hoc ln -s lines. The names are @compoundingtech/pty and @compoundingtech/smalltalk, matching package.json/package-lock.json after 795b9becoord is gone entirely.
  • Completions are generated at build time from the just-built binary and installed with installShellCompletion, so what ships can never drift from src/command-table.ts.
  • meta.mainProgram = "convoy".
  • # TODO(rust): markers on the three things a Rust rewrite deletes: npmDepsHash, the sibling-symlink block, and the strip-types bin shim.

Why flake-check-as-CI, not a Node workflow

convoy depends on smalltalk and pty via file:../sibling. A plain npm ci in CI needs both repos checked out next to convoy — there is no clean way for a GitHub workflow to arrange that. Flake inputs solve it properly: the sibling packages come from their own flakes, pinned in flake.lock.

.github/workflows/nix.yml is modeled on pty's existing one (PR + push to main, ubuntu-latest, DeterminateSystems/determinate-nix-action@v3) but runs nix flake check rather than nix build, so the checks actually gate rather than only the package building.

Checks — all against a real built binary

check what it proves
help convoy --help runs from the installed binary
completions generates bash/zsh/fish, asserts each is non-empty, that fish still binds to convoy, and that each shell accepts its own generated script (bash -n / zsh -n / fish -n)
typecheck convoy's own npm run typecheck (tsc --noEmit), against the same sibling deps the package links
parity src/completions.test.ts — the suite that keeps the command table, argv dispatch, and the generated scripts from drifting. Nothing ran it automatically before this

checks.completions is where zsh actually gets syntax-checked — it is often absent from a dev machine, so the unit test in #85 skips it locally. Here it is guaranteed.

Gap, stated honestly

The full vitest suite is not wired up as a check — only the completions parity file. Parts of the rest shell out to git worktree and probe the host, which the Nix sandbox cannot provide, so forcing it in would mean either a red check or carving the suite up, neither of which belongs here. So nix flake check covers build + help + completions + typecheck + the parity suite, and the remaining npm test coverage stays a local step. Worth revisiting by splitting the host-dependent tests behind a marker.

Verification (observed, on x86_64-linux)

  • nix build .#default — succeeds. npmDepsHash = "sha256-RCsVWvgt0853fww5A89GDDd9OR2aexf23dAnn7EDq1M=", computed by letting the build fail on a placeholder and taking the reported got: value.
  • nix flake check — passes: checks.help, checks.typecheck, checks.completions, checks.parity all green. The parity check's log shows 31 passed, with the bash/zsh/fish syntax assertions actually running rather than skipping.
  • ./result/bin/convoy --help — prints the subcommand listing.
  • ./result/bin/convoy --version0.2.0-ts.0.
  • ./result/bin/convoy completions fish — 186 lines; bash/zsh also emit and pass their -n check inside the sandbox.
  • Completions land at share/bash-completion/completions/, share/fish/vendor_completions.d/, share/zsh/site-functions/.

nix flake check was only run for the host system; the other platforms are evaluated but not built (standard eachDefaultSystem behavior).

schickling-assistant and others added 2 commits July 20, 2026 17:32
convoy had no flake and no CI. This adds both, and they are the same thing:
`nix flake check` is the gate.

The flake is thin and self-contained — nixpkgs, flake-utils, and the two
sibling packages from their own flakes. It builds from `self`, so it packages
whatever revision it ships with rather than a pinned fetchFromGitHub.

Why a flake check and not a Node workflow: convoy depends on smalltalk and pty
via `file:../sibling`, so a plain `npm ci` in CI needs those repos checked out
next to convoy. Flake inputs solve that properly — the sibling packages come
from their own flakes, pinned in flake.lock, and the sibling node_modules links
are expressed declaratively as an npm-name -> store-path attrset.

Checks (all run a REAL built binary, not a source tree):
  - help        — `convoy --help`
  - completions — generates bash/zsh/fish, asserts each is non-empty, that fish
                  still binds to `convoy`, and that each shell accepts its own
                  generated script (`-n`). This is where zsh actually gets
                  syntax-checked; it is often absent from a dev machine.
  - typecheck   — convoy's own `tsc --noEmit`, against the same sibling deps.

The vitest suite is deliberately not wired up: parts of it shell out to
`git worktree` and probe the host, which the sandbox cannot provide.

Completions are generated at build time from the just-built binary and
installed via installShellCompletion, so what ships can never drift from
src/command-table.ts.

The smalltalk input points at the branch of compoundingtech/smalltalk#104 (the
PR adding smalltalk's flake) because that flake does not exist on its main yet;
there is a TODO to repoint once it merges.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T1xLDkqUDCYUMMADdMqVgP
agent-session-id: 312caff5-3274-4d97-baa4-8ff06ab03fc5
agent-tool: Claude Code
agent-tool-version: 2.1.215
agent-model: claude-opus-4-8
agent-runtime-profile: /nix/store/acr8a3l2v366jgmwiq8xdrhgz1py0db5-coding-agent-runtime-profile/share/coding-agents/profile.json
agent-skills-manifest: /nix/store/sj1v5j91h8v8d1w9lca4040302lwrd6v-agent-skills-corpus/share/agent-skills/manifest.json
tooling-profile: dotfiles@unknown-dirty
The suite in src/completions.test.ts is what keeps the command table, argv
dispatch, and the generated scripts from drifting — but nothing ran it
automatically. It is sandbox-safe (it only spawns `convoy` and reads
src/cli.ts), unlike the rest of the vitest suite, which shells out to
`git worktree` and probes the host.

bash, zsh, and fish are in the check's inputs so its per-shell syntax
assertions actually run instead of skipping.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T1xLDkqUDCYUMMADdMqVgP
agent-session-id: 312caff5-3274-4d97-baa4-8ff06ab03fc5
agent-tool: Claude Code
agent-tool-version: 2.1.215
agent-model: claude-opus-4-8
agent-runtime-profile: /nix/store/acr8a3l2v366jgmwiq8xdrhgz1py0db5-coding-agent-runtime-profile/share/coding-agents/profile.json
agent-skills-manifest: /nix/store/sj1v5j91h8v8d1w9lca4040302lwrd6v-agent-skills-corpus/share/agent-skills/manifest.json
tooling-profile: dotfiles@unknown-dirty
@schickling
schickling marked this pull request as ready for review July 20, 2026 19:35
@schickling
schickling merged commit f90e2d1 into schickling-assistant/2026-07-20-completions Jul 20, 2026
1 check passed
schickling-assistant added a commit that referenced this pull request Jul 21, 2026
…ent (#87) (#91)

* feat(nix): self-contained flake + `nix flake check` as CI

convoy had no flake and no CI. This adds both, and they are the same thing:
`nix flake check` is the gate.

The flake is thin and self-contained — nixpkgs, flake-utils, and the two
sibling packages from their own flakes. It builds from `self`, so it packages
whatever revision it ships with rather than a pinned fetchFromGitHub.

Why a flake check and not a Node workflow: convoy depends on smalltalk and pty
via `file:../sibling`, so a plain `npm ci` in CI needs those repos checked out
next to convoy. Flake inputs solve that properly — the sibling packages come
from their own flakes, pinned in flake.lock, and the sibling node_modules links
are expressed declaratively as an npm-name -> store-path attrset.

Checks (all run a REAL built binary, not a source tree):
  - help        — `convoy --help`
  - completions — generates bash/zsh/fish, asserts each is non-empty, that fish
                  still binds to `convoy`, and that each shell accepts its own
                  generated script (`-n`). This is where zsh actually gets
                  syntax-checked; it is often absent from a dev machine.
  - typecheck   — convoy's own `tsc --noEmit`, against the same sibling deps.

The vitest suite is deliberately not wired up: parts of it shell out to
`git worktree` and probe the host, which the sandbox cannot provide.

Completions are generated at build time from the just-built binary and
installed via installShellCompletion, so what ships can never drift from
src/command-table.ts.

The smalltalk input points at the branch of compoundingtech/smalltalk#104 (the
PR adding smalltalk's flake) because that flake does not exist on its main yet;
there is a TODO to repoint once it merges.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T1xLDkqUDCYUMMADdMqVgP
agent-session-id: 312caff5-3274-4d97-baa4-8ff06ab03fc5
agent-tool: Claude Code
agent-tool-version: 2.1.215
agent-model: claude-opus-4-8
agent-runtime-profile: /nix/store/acr8a3l2v366jgmwiq8xdrhgz1py0db5-coding-agent-runtime-profile/share/coding-agents/profile.json
agent-skills-manifest: /nix/store/sj1v5j91h8v8d1w9lca4040302lwrd6v-agent-skills-corpus/share/agent-skills/manifest.json
tooling-profile: dotfiles@unknown-dirty

* feat(nix): run the completions parity suite as a flake check

The suite in src/completions.test.ts is what keeps the command table, argv
dispatch, and the generated scripts from drifting — but nothing ran it
automatically. It is sandbox-safe (it only spawns `convoy` and reads
src/cli.ts), unlike the rest of the vitest suite, which shells out to
`git worktree` and probes the host.

bash, zsh, and fish are in the check's inputs so its per-shell syntax
assertions actually run instead of skipping.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T1xLDkqUDCYUMMADdMqVgP
agent-session-id: 312caff5-3274-4d97-baa4-8ff06ab03fc5
agent-tool: Claude Code
agent-tool-version: 2.1.215
agent-model: claude-opus-4-8
agent-runtime-profile: /nix/store/acr8a3l2v366jgmwiq8xdrhgz1py0db5-coding-agent-runtime-profile/share/coding-agents/profile.json
agent-skills-manifest: /nix/store/sj1v5j91h8v8d1w9lca4040302lwrd6v-agent-skills-corpus/share/agent-skills/manifest.json
tooling-profile: dotfiles@unknown-dirty

* fix(nix): repoint the smalltalk input at main

compoundingtech/smalltalk#104 has merged, so smalltalk's flake now exists
on main and the PR-branch pin is no longer needed. Drops the TODO that
asked for exactly this.

The pty input already pointed at plain `github:compoundingtech/pty`
(main) and carried no TODO, so it is left untouched.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T1xLDkqUDCYUMMADdMqVgP
agent-session-id: 312caff5-3274-4d97-baa4-8ff06ab03fc5
agent-tool: Claude Code
agent-tool-version: 2.1.215
agent-model: claude-opus-4-8
agent-runtime-profile: /nix/store/acr8a3l2v366jgmwiq8xdrhgz1py0db5-coding-agent-runtime-profile/share/coding-agents/profile.json
agent-skills-manifest: /nix/store/sj1v5j91h8v8d1w9lca4040302lwrd6v-agent-skills-corpus/share/agent-skills/manifest.json
tooling-profile: dotfiles@unknown-dirty

* fix(nix): lock smalltalk onto the post-#105 main tip

The flake pinned smalltalk at ec24ac4, whose npmDepsHash was stale --
smalltalk main was broken standalone, so every `nix flake check` here
failed on an input it does not own. smalltalk#105 fixed that hash on
main; this moves the lock onto that merge commit (850266f).

Only the smalltalk node moves. Verified locally: `nix build .#default`
succeeds, `nix flake check` is green on all four checks (help,
completions, parity, typecheck), and the built `convoy --help` and
`convoy completions fish` both work.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T1xLDkqUDCYUMMADdMqVgP
agent-session-id: 312caff5-3274-4d97-baa4-8ff06ab03fc5
agent-tool: Claude Code
agent-tool-version: 2.1.215
agent-model: claude-opus-4-8
agent-runtime-profile: /nix/store/acr8a3l2v366jgmwiq8xdrhgz1py0db5-coding-agent-runtime-profile/share/coding-agents/profile.json
agent-skills-manifest: /nix/store/sj1v5j91h8v8d1w9lca4040302lwrd6v-agent-skills-corpus/share/agent-skills/manifest.json
tooling-profile: dotfiles@unknown-dirty

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants