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:
@@ -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.
|
||||
|
||||
@@ -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
@@ -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.
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
Executable
+75
@@ -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"
|
||||
Reference in New Issue
Block a user