feat(scripts): make nix develop work in cloud sessions without touching the flake

Nix's `github:` fetcher wants a tarball from
github.com/<owner>/<repo>/archive/<rev>.tar.gz, and that endpoint is not
served in a cloud session. Three things that look like fixes are not:

  - Raising the environment's network access to Full, or allowlisting
    codeload.github.com, changes nothing: GitHub traffic bypasses the
    domain allowlist entirely and goes through a proxy that scopes API
    and release-asset requests to repositories attached to the session.
    An input from any other owner gets a 403.
  - Attaching the dependency repos is refused -- cross-tier adds are not
    supported once the session has sources from another owner.
  - Nix has no setting that makes `github:` inputs use the git transport.

The lock's own integrity machinery is what works. flake.lock records a
narHash per input, and Nix looks a source up in the store and its
substituters before fetching anything. The git and tarball fetchers
produce byte-identical trees for a revision, so prefetching the locked
rev over git+https lands the input at exactly the store path the lock
names:

  lock narHash:  sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=
  git prefetch:  sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=

scripts/nix-seed-flake-inputs.sh does that for every locked input. It
reads flake.lock and never writes it. Nix still verifies each tree
against the recorded hash, so this satisfies the lock rather than working
around it -- a mismatch fails loudly. Rewriting the lock's nodes to `git`
type and carrying the old narHash across would have been the opposite:
that hash is the claim the lock exists to make.

Verified from a fresh clone with a cleared git cache: 15/15 inputs
seeded, then `nix develop` gives rustc 1.98.1, cargo and crate2nix, with
flake.nix and flake.lock unmodified.

Not wired into the session-start hook: it costs minutes and several GB
once the devShell builds, and most work only needs the preinstalled cargo
toolchain.

Corrects three places that claimed nix could not work here --
context/cloud-agents.md, the design-review skill's kb.pl check, and the
hook's environment notes -- all written before the git transport was
tested. The doc also records the two alternatives, a binary-cache
substituter and a self-hosted environment.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DyHK7L8S1XDX3EihTnSKaX
This commit is contained in:
Claude
2026-09-19 06:52:17 +00:00
parent 7936844b6c
commit 8cf8f01f12
5 changed files with 136 additions and 9 deletions
+3 -2
View File
@@ -126,8 +126,9 @@ fi
# ── 4. Environment facts worth knowing up front ────────────────────────────
cat <<'NOTES'
[session-start] Environment notes (see context/cloud-agents.md):
• Use the preinstalled cargo toolchain. `nix develop` cannot resolve this
flake here — the proxy blocks GitHub tarball fetches (403).
• Use the preinstalled cargo toolchain for ordinary work. `nix develop`
also works, but only after scripts/nix-seed-flake-inputs.sh — GitHub
tarballs are not served here, so the locked inputs are seeded over git.
• `bd dolt push` is blocked outbound (403). Bead status changes stay local.
• Never run a bare `bd init` in this repo: it rewrites AGENTS.md, CLAUDE.md
and .claude/settings.json, and commits. Use --skip-agents --skip-hooks.
+6 -3
View File
@@ -103,9 +103,12 @@ nix shell nixpkgs#trealla --command tpl prolog-sessions/kb.pl \
-g "(kb_consistent -> halt(0) ; (kb_report, halt(1)))"
```
In a cloud container that command does not resolve — nixpkgs comes in as a
GitHub tarball and the proxy refuses it (`context/cloud-agents.md`). Review
`kb.pl` edits by reading, and let CI run the check.
In a cloud container that needs one step first: `nix shell` resolves nixpkgs
as a GitHub tarball, which is not served there. Run
`scripts/nix-seed-flake-inputs.sh` to seed the locked inputs over git (it does
not modify `flake.lock`), after which the command works. Details and the two
alternatives are in `context/cloud-agents.md`. If you would rather not spend
the time, review `kb.pl` edits by reading and let CI run the check.
## Closing the loop
+51 -4
View File
@@ -60,15 +60,62 @@ same if you need to re-sync by hand; never run it in the working tree.
| Thing | Status | Use instead |
|---|---|---|
| `nix develop` | Fails: flake inputs are GitHub **tarballs**, and the proxy answers 403 | The preinstalled `cargo`/`rustc` — same stable toolchain the flake pins |
| `nix develop` **unseeded** | Fails: flake inputs are GitHub **tarballs**, which are not served here | Run `scripts/nix-seed-flake-inputs.sh` first — see below. Or just use the preinstalled `cargo`/`rustc`, the same stable toolchain the flake pins |
| `bd dolt push` | Fails: 403 outbound | Nothing; bead status changes stay local. Say so rather than implying the tracker was updated |
| `git+ssh://` Dolt remote in `config.yaml` | No ssh client in the container | `git+https://<origin>`, which the hook passes explicitly |
| `git push`, GitHub API | **Work normally** | — |
Nix itself installs and runs fine (no init system, so the daemon needs
starting by hand); only flake-input resolution is blocked. Reads of public
GitHub repos over the git protocol work — it is specifically the
`/archive/*.tar.gz` and `codeload` endpoints that are refused.
starting by hand). Reads of public GitHub repos over the git protocol work —
it is specifically the `/archive/*.tar.gz` and `codeload` endpoints that are
refused.
### Making `nix develop` work
`scripts/nix-seed-flake-inputs.sh` makes the devShell work **without touching
`flake.nix` or `flake.lock`**. Verified end to end: `rustc 1.98.1`, `cargo`,
`crate2nix` in the shell, `git status` clean.
Why it is needed, and why the obvious fixes are not available:
- Nix's `github:` fetcher wants a tarball. GitHub traffic **bypasses the
environment's domain allowlist entirely** and goes through a proxy that
scopes API and release-asset requests to repositories *attached to the
session*. A flake input from any other owner gets a 403. Raising network
access to **Full**, or allowlisting `codeload.github.com`, therefore does
nothing.
- Attaching the dependency repos is refused: `cross-tier adds are not
supported in v1` once the session already has sources from another owner.
- Nix has no setting that makes `github:` inputs use the git transport.
What does work is the lock's own integrity machinery. `flake.lock` records a
`narHash` per input, and Nix looks a source up in the store and its
substituters *before* fetching. The git and tarball fetchers produce
byte-identical trees for a revision, so a `git+https` prefetch at the locked
rev lands the input at exactly the store path the lock names:
```
lock narHash: sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=
git prefetch: sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=
```
The script does that for every locked input. Nix still verifies each tree
against the recorded hash, so this **satisfies** the lock rather than working
around it — a mismatch fails loudly. Do not instead rewrite the lock's nodes
to `git` type: carrying the old `narHash` onto a different fetcher is exactly
the claim the lock exists to make.
It is not in the session-start hook because it is expensive (minutes, plus
several GB of store once the devShell builds) and most work does not need the
devShell. Run it when you do.
Two alternatives if per-container seeding is unattractive: push the inputs to
a binary cache with `nix flake archive` and point sessions at it with
`NIX_CONFIG`/`extra-substituters` plus a **Custom** allowlist entry for the
cache host (not GitHub, so the allowlist does apply); or use a self-hosted
environment, where egress leaves through your own network boundary.
`cache.nixos.org` is reachable, so the build itself substitutes normally.
---
+1
View File
@@ -274,6 +274,7 @@ examples/pretty-print.rs Parse stdin, print formatted .improv
### Context docs
```
.claude/skills/design-review/ Review skill: checks a change against the doctrine in these docs + kb.pl
scripts/nix-seed-flake-inputs.sh Seeds locked flake inputs over git so `nix develop` works in cloud sessions
prolog-sessions/kb.pl Executable knowledge base: invariants, doctrine, decisions (CI-checked)
context/cloud-agents.md Cloud-session environment: tracker sync, bd pitfalls, what is blocked
context/design-principles.md Architectural principles & testing doctrine
+75
View File
@@ -0,0 +1,75 @@
#!/usr/bin/env bash
# Seed the Nix store with this flake's locked inputs, fetched over git.
#
# Why this exists: Nix's `github:` fetcher downloads a repository as a tarball
# from github.com/<owner>/<repo>/archive/<rev>.tar.gz. In a Claude Code cloud
# session that endpoint is not served — GitHub traffic bypasses the
# environment's domain allowlist and goes through a proxy that scopes API and
# release-asset requests to repositories attached to the session, so a flake
# input from any other owner gets a 403. Raising network access to Full does
# not help, because GitHub never goes through that allowlist.
#
# The git protocol IS served. And `flake.lock` already records a narHash for
# every input, which Nix looks up in the store and its substituters before
# fetching anything (see `nix flake` in the Nix manual). The git and tarball
# fetchers produce byte-identical trees for a given revision, so fetching over
# git lands the input at exactly the store path the lock names.
#
# So this script reads flake.lock and fetches each locked revision over
# git+https. It never writes to flake.lock. Nix still verifies every tree
# against the narHash the lock already records: a mismatch fails loudly here
# rather than being papered over. Afterwards `nix develop` finds every input
# in the store and never reaches for a tarball.
#
# Cost: a few minutes and a few hundred MB of git cache on a cold container;
# the full devShell build is several GB more. Run it when you actually need
# the devShell — the preinstalled cargo toolchain covers ordinary work.
#
# Usage: scripts/nix-seed-flake-inputs.sh && nix develop
set -uo pipefail
cd "$(dirname "${BASH_SOURCE[0]}")/.." || exit 1
for tool in nix jq; do
command -v "$tool" >/dev/null 2>&1 || { echo "error: $tool not found on PATH" >&2; exit 1; }
done
[ -f flake.lock ] || { echo "error: no flake.lock here" >&2; exit 1; }
# Nix needs the proxy CA when it talks to github over https.
if [ -z "${NIX_SSL_CERT_FILE:-}" ] && [ -r /root/.ccr/ca-bundle.crt ]; then
export NIX_SSL_CERT_FILE=/root/.ccr/ca-bundle.crt
fi
NIX_FLAGS=(--extra-experimental-features 'nix-command flakes')
mapfile -t INPUTS < <(
jq -r '.nodes | to_entries[] | .value.locked
| select(.type == "github" and .rev)
| "git+https://github.com/\(.owner)/\(.repo)?rev=\(.rev)&shallow=1"' flake.lock \
| sort -u
)
if [ "${#INPUTS[@]}" -eq 0 ]; then
echo "no github-sourced inputs in flake.lock; nothing to seed"
exit 0
fi
echo "seeding ${#INPUTS[@]} locked inputs over git (flake.lock is not modified)…"
failed=0
for url in "${INPUTS[@]}"; do
rev="${url##*rev=}"; rev="${rev%%&*}"
label="${url#git+https://github.com/}"
label="${label%%\?*}@${rev:0:7}"
if nix "${NIX_FLAGS[@]}" flake prefetch --json "$url" >/dev/null 2>&1; then
printf ' ok %s\n' "$label"
else
printf ' FAIL %s\n' "$label"
failed=$((failed + 1))
fi
done
if [ "$failed" -gt 0 ]; then
echo "$failed input(s) could not be seeded; 'nix develop' will still try a tarball for those." >&2
exit 1
fi
echo "done — 'nix develop' should now resolve without touching the tarball endpoint"