This repository was archived by the owner on Jul 24, 2026. It is now read-only.
feat(nix): add self-contained flake with generated shell completions - #104
Merged
Conversation
Packaging for this repo lived out-of-tree in a private dotfiles wrapper pinned to an old rev, so the repo's own HEAD was never buildable on its own. This moves the derivation in-tree with only public inputs (nixpkgs, flake-utils, compoundingtech/pty). Completions for bash, zsh and fish are generated at build time by the just-built binary via `st completions <shell>`, so they cannot drift from the command tree in src/commands/completions.ts. 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
marked this pull request as ready for review
July 20, 2026 15:18
This repo had no CI at all. Mirror compoundingtech/pty's nix.yml, but run `nix flake check` rather than `nix build` so the flake's `checks.help` and `checks.completions` actually gate PRs instead of only the package building. 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
`nix flake check` previously only exercised the built binary (help, completions). Add `checks.typecheck`, which runs the repo's own `npm run build` (typecheck-only: both tsconfigs set `noEmit`) over src/ and examples/. This needs devDependencies, which the shipped package omits. Two sandbox obstacles, both documented at the `mkDevCheck` definition: npm cannot resolve the `file:../pty` devDependency through a store symlink (it chmods the read-only linked bin and fails EPERM), so the sibling is copied to the relative path the lockfile names; and `--ignore-scripts` keeps node-pty's native build out of the sandbox. The vitest `unit` project is not gated yet. 1322/1323 pass in the sandbox; the holdout asserts a `+<short-sha>` version suffix resolved by shelling out to `git rev-parse`, which a hermetic build has no `.git` for. That is the build-identity question tracked in #103. 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
This was referenced Jul 20, 2026
schickling-assistant
added a commit
that referenced
this pull request
Jul 20, 2026
PR #104 added the flake with an npmDepsHash computed against the package-lock.json on its own branch. Meanwhile 12a4772 ("sync stale package-lock.json to package.json (0.3.0)") landed on main and changed that lock. The merge of #104 combined the new flake with main's newer lock, so the hash was stale from the moment it landed -- a semantic conflict a text-level merge cannot see. main's Nix workflow has been red on the merge commit (run 29775629348) ever since. Regenerated with the command the flake itself documents: nix run nixpkgs#prefetch-npm-deps -- package-lock.json Verified locally: `nix build .#default` succeeds and `nix flake check` passes all three checks (help, completions, typecheck). 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
This was referenced Jul 20, 2026
schickling-assistant
added a commit
to compoundingtech/convoy
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to subscribe to this conversation on GitHub.
Already have an account?
Sign in.
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Adds a self-contained
flake.nixat the repo root, sonix buildworks on this repo's own HEAD — and wires it up as this repo's first CI.Outputs:
packages.smalltalk/packages.default—standsmalltalkbins,meta.mainProgram = "st"checks.help— smoke test runningst --helpchecks.completions— asserts every installed shell yields a non-empty script, and that fish output still binds tostchecks.typecheck— runs the repo's ownnpm run build(typecheck-only) oversrc/andexamples/devShells.defaultInputs are
nixpkgs,flake-utils, andgithub:compoundingtech/pty(nixpkgs follows). Nothing private, so anyone can build this standalone.Why
Packaging for this repo currently lives out-of-tree in a private dotfiles wrapper, pinned to a fixed old rev via a
smalltalk-srcinput. That means the repo's own HEAD was never buildable on its own and the packaging silently drifted from the source — the wrapper still symlinked the pre-rename@myobie/ptypath, whilepackage.json/package-lock.jsonnow name the dep@compoundingtech/pty. This flake targets the current tree (src = self) and uses the current name.Version is read from
package.jsonrather than hardcoded. Reconciling that with a richer build identity is tracked in #103.CI
.github/workflows/nix.ymlis the repo's first CI, modeled oncompoundingtech/pty'snix.yml: PR + push-to-main,ubuntu-latest,DeterminateSystems/determinate-nix-action@v3.It runs
nix flake check, notnix build— the distinction is the point.nix buildwould only prove the package compiles;nix flake checkadditionally evaluates everychecks.*, sohelp,completions, andtypecheckall gate the PR. Two of those exercise a real built binary: the completions contract is verified by invokingst completions <shell>on the artifact that was just built, not by inspecting committed files.The flake is also what makes CI tractable at all here.
package.jsondeclares"@compoundingtech/pty": "file:../pty"— a sibling-checkout dependency. A plain Node workflow would have to clone pty next door at some manually-chosen rev and keep that in sync by hand; as a flake input, pty is resolved and pinned byflake.locklike any other dependency.What CI does not cover
nix flake checkdoes not run this repo's vitest suites. To be precise about why, since it is not the reason you would guess:unit— this very nearly works. Installing devDependencies in the sandbox turned out to be surmountable (seemkDevCheckin the flake), and 1322 of 1323 unit tests pass there. The single holdout istests/unit/cli.test.ts, which assertsst <semver>+<short-sha>; the SHA is produced by shelling out togit rev-parsein the source dir, and a hermetic build has no.git. The CLI degrades gracefully by design — it is only the test that requires a git checkout. Making this green means settling build identity, i.e. Adopt the shared build-identity contract: st --version has no build identity, MCP serverInfo reports a different version #103, so it is deliberately left out rather than papered over.integration— not attempted, and shouldn't be: it shells out to rsync and spawns real pty sessions.So: treat "CI green" as "it builds, the binary runs, completions are intact, and it typechecks" — not as "the test suite passed". Closing the unit gap is a viable follow-up; the cheapest version is skipping that one environment-dependent assertion when git is unavailable, which is a change to the test file and so out of scope for this PR.
Completions contract
Completions are generated at build time by the just-built binary (
st completions <shell>), not committed, so they cannot drift from the command tree insrc/commands/completions.ts. All three shells the generator supports are installed:share/bash-completion/completions/st.bashshare/zsh/site-functions/_stshare/fish/vendor_completions.d/st.fishThe generator hardcodes
complete -c st(and the zsh/bash equivalents), so the scripts are always forstregardless of which bin name invoked them — hence--cmd stand thest.fishfilename.checks.completionsgreps for^complete -c stso a change that broke that binding would fail the check rather than silently ship dead completions.Notes
file:dep link is expressed as a smallnpm-name -> store pathattrset mapped intoln -slines, so adding a second sibling repo later is a one-line change.checks.typecheckneeds devDependencies, which the shipped package omits. Two sandbox obstacles, both documented atmkDevCheck: npm cannot resolvefile:../ptythrough a store symlink (it chmods the linkedbin/pty, which is read-only in the store, and failsEPERM), so the sibling is copied to the relative path the lockfile names and made writable; and--ignore-scriptskeeps node-pty's native build out of the sandbox.npmDepsHashis unchanged —prefetch-npm-depsalready hashes the full lockfile, and--omit=devis install-time only.# TODO(rust):markers flag the three things a Rust rewrite deletes: thenpmDepsHash, the sibling-symlink block, and thenode --experimental-strip-typesbin shims._ST_INVOKED_AS, matching whatbin/stdoes, sosmalltalk --helpself-identifies correctly.standsmalltalk(exactlypackage.json'sbin). The dotfiles wrapper also emitted a legacycoordalias, but this repo has zero references to it. No live regression: the dotfiles wrapper is unchanged for now./resultto.gitignore.Verification
nix build .#defaultsucceeds.npmDepsHash = "sha256-CfgSssHEdSjL9liKjaw9aYKMtCJdaxWKGbFM59PgNwg="(computed vianix run nixpkgs#prefetch-npm-deps -- package-lock.json, confirmed by the build; the twofile:../ptyentries are expectedly uncacheable and are supplied by the store symlink instead).nix flake checkpasses locally —help,completions, andtypecheckall green.yamllint, zero errors) and parsed before pushing.st --help,smalltalk --help, andst completions fishall verified against the built output; the fish script starts withcomplete -c st -e.