First-Run Image Pull (minutes → seconds), Serve Background+SSE, Gemini Agent, Patchright Chromium Extensions (CapSolver pinned), Docker Disk Reclaim, Login Cookie Freshness, Claude Code 2.1.158#37
Merged
Conversation
…i Agent, Patchright Chromium Extensions (CapSolver pinned), Docker Disk Reclaim, Login Cookie Freshness, Claude Code 2.1.158
- feat(cmd): `cell <agent>` default acquisition flips from "build whatever's needed" to "pull pure → pull impure → build pure → build impure" — fresh installs no longer wait through a multi-minute docker build whose output the orchestrator would discard on the next step; `--impure` opts back into the legacy Dockerfile path, `--debian` is a deprecated alias kept for one release, `--pure` is a silent no-op for back-compat
- feat(cmd): `cell build` defaults to the pure (nix2container) path; same flag set as agents (`--impure`, `--debian`, `--pure`) plus `--update` for flake refresh and `--no-generate` to skip build-context regen
- feat(cmd): `cell build --stack <name>` and `DEVCELL_STACK=<name>` env override `[cell].stack` for a single build — precedence flag > env > TOML > "base" — no more editing `.devcell.toml` to test a different stack image
- feat(cmd): new `cell build df` — read-only Docker disk-usage table ranked by reclaimable bytes; rows tag pinned-by-running-container vs reclaimable, surface per-row reclaim commands as hints, never execute them; `--top`/`--all`/`--kind`/`--json` for narrowing and scripting
- feat(cmd): new `cell build prune` — composes an ordered cleanup plan for the current host (GOOS), docker vs nix path, and rootless-aware on Linux; default sequence is conservative, `--force` opts into nuclear cleanup (Docker Desktop wipe / qcow nuke / aggressive nix GC); confirms before executing
- feat(cmd): new `cell gemini` agent alongside claude/codex/opencode — same flag-stripping shell, same per-agent config layout; new `--nix-daemon` flag (cross-agent) keeps the in-container nix-daemon running so users can `nix profile install` ad-hoc fixes from a live cell
- feat(cmd): `cell login` flushes the Chrome Cookies sqlite WAL between phase 1 (interactive login) and phase 2 (CDP extraction) so phase 2 reads a consistent snapshot, warns the user when the Cookies mtime is older than the session start ("did you actually log in?"), and SIGTERMs `mcp-server-patchright` in every running cell sharing the same cell-home bind mount so Claude/etc. pick up the fresh storage-state.json on the next browser tool call — no more stale-cookie surprises after relog
- feat(cmd): `cell login` writes storage state to `$HOME/.playwright/storage-state.json` (DIMM-208 layout) and pre-creates the parent dir so first-run can't race
- feat(cmd): root-cmd persistent pre-run loads `[cell].stack`/`modules`/`per_session_image` into runner globals BEFORE any subcommand's RunE — `cell build` now tags images with the project's actual stack instead of always `devcell-user:base-pure`
- feat(serve): `cell serve` POST `/v1/responses` accepts `"background": true` → returns 202 + job stub; clients poll GET `/v1/responses/{id}` until status is terminal (`completed`/`failed`/`cancelled`); in-memory `JobStore` tracks lifecycle and exposes a `CancelFunc` per job for future POST `/v1/responses/{id}/cancel`
- feat(serve): `cell serve` POST `/v1/responses` accepts `"stream": true` for the claude agent and returns Server-Sent Events with token-level deltas (opencode still buffers); combining `stream + background` returns 400 with `unsupported_combination` — clients pick one mode
- feat(serve): new GET `/v1/responses/{id}` handler — same `ResponsesObject` shape whether the underlying job is still in-progress (empty Output) or terminal (fully populated, identical to a sync POST response)
- feat(runner): new `AcquireImage` orchestrator walks the action sequence from `DecideLaunchActionsPure`, invokes one closure per effective action (`PullPure`/`PullImpure`/`BuildPure`/`BuildImpure`), stops at first success, and surfaces `errors.Join`-ed chain on total failure — every fallback attempted is visible to the user, no silent reverts
- feat(runner): `DecideLaunchActionsPure` returns an ordered sequence (replaces the singular `DecideLaunchActionPure`) and models fallback as data (`ActionPullPure`/`ActionPullImpure`/`ActionBuildPure`/`ActionBuildImpure`/`ActionUseLocal`/`ActionDryRun`) so the launcher's decision logic is unit-testable without invoking docker or nix
- feat(runner): new `PullAndTagImpure` dual-tags an impure pull as both `UserImageTag()` and `UserImageTagPure()` — a subsequent default-path launch finds the image locally without retrying the pull chain
- feat(runner): new `UserImageTagPure()` (`devcell-user:<stack>-pure`), `StackImageTagPure(stack)`, `StackImageTagImpure(stack)` registry tags; deprecated `StackImageTagDebian` aliases impure for one release; `PickImageTag(impure bool)` is the single seam every runtime caller uses to choose a local image variant
- feat(runner): new `PreflightNixBuilder` (+ `PreflightNixBuilderFromProbe` testable seam) extracted from the BuildImagePure body — callers probe host nix usability before deciding to attempt a pure build; pull-only cold starts on Mac no longer trigger the linux-builder gate
- feat(runner): new pure-build path (`pure_build.go`) invokes `nix2container`'s `copyToDockerDaemon` via skopeo's `nix:` transport; supports both local-flake (`path:`) and remote-flake (`github:`) refs; tees progress to a heartbeat goroutine; no docker-build fallback inside the pure path (failures surface, do not silently revert)
- feat(runner): new pure-nixhome-resolver — resolves a local `path:` flake from the project tree, falls back to the upstream `github:DimmKirr/devcell?dir=nixhome` flake when no local nixhome exists (mirrors scaffold's Dockerfile FROM-image fallback)
- feat(runner): new disk-space introspection (`df.go` + `df_collect.go`) — `docker system df -v` JSON parsed into ranked entries, image dedup via unique-blob accounting, pin-count via running-container join; emits stable JSON schema for scripting
- feat(runner): new prune planner — per-(GOOS × Force × Pure × Rootless) command plan composed in pure builders, executed by `RunDockerPrune`/`RunNixPrune` after confirmation; rootless detected via `docker info` at the call site
- feat(runner): new build-debug helper — `cell build --debug` tees output to a file and a ring buffer for post-mortem artifact capture
- feat(runner): new registry probe — best-effort ECR/GHCR reachability check that powers the pull-pure → pull-impure decision (offline hosts skip pulls and go straight to local build)
- feat(runner): warm-boot path no longer pays for `mise reshim` + recursive chowns over the persistent `~/.local/share/mise` (~17k entries) when `~/.tool-versions` is unchanged — drops macOS launches from ~57s to ~10s
- feat(runner): kick-mcp helper SIGTERMs `mcp-server-patchright` in every running cell that shares the current cell-home bind mount; best-effort, never errors out the calling command
- feat(runner): registry pull falls back to docker-build when ECR is unreachable, and first-run no longer wastes minutes on a docker build the next step ignores
- feat(scaffold): `SyncNixhome` runs `git init` + `git add .` inside the synced copy so nix's flake source filter (which uses git ls-files) sees every file — fixes the case where dest lives inside a gitignored dir (`.devcell/`) and nix would otherwise see an empty tree
- feat(scaffold): scaffolding the first-run `.devcell.toml`/`.devcell/` no longer eagerly invokes `buildImageWithSpinner` — image acquisition is owned by the unified orchestrator below it
- feat(nix): new `nixhome/packages/image.nix` (911 lines) — nix2container OCI image builder; content-addressed; bakes home-manager `activationPackage` into the image filesystem without running activation at runtime; per-stack outputs keyed on HOST system (not target) so IFD helpers run on the right arch; optional `includeNix` (default on) keeps the runtime-installable nix profile capability
- feat(nix): `nix2container` flake input added; `skopeo-nix2container` overlay forces unstable's skopeo 1.21+ for the `nix:` transport while keeping `nix2container-bin` on the main nixpkgs pin (avoids a 0-byte closure-graph.json regression from a wholesale pkgs swap)
- feat(nix): flake `lib.mkHome` now threads `self` into `extraSpecialArgs` so downstream modules can resolve the flake source path for content-addressed staging
- feat(nix): new `nixhome/entrypoint.sh` for the pure-image runtime — reads `/etc/devcell/metadata.json` for stack/module/package counts and prefers runner-injected `DEVCELL_BUILD_DATE`/`DEVCELL_BUILD_REV` (sourced from OCI labels) so post-2026-05-16 cells display real provenance instead of placeholders; pre-2026 image fall-through preserved
- feat(nix): `base.nix` exports `NIX_LD` pointing at the real nix glibc loader (`ld-linux-aarch64.so.1` or `ld-linux-x86-64.so.2` per arch) so non-nix binaries (mise-installed node/go, pip wheels, downloaded gpg keychains) load against a consistent loader on both pure and impure images
- feat(nix): `base.nix` skips the impure `stageEntrypoints` rsync activation when `/etc/devcell/.image-built-with-nix2container` marker is present — pure images already have fragments staged at build time
- feat(nix): new entrypoint fragment `04-nix-daemon.sh` boots a per-cell `nix-daemon` when the agent flag `--nix-daemon` is passed, enabling runtime `nix profile install` / `home-manager switch` for ad-hoc fixes
- feat(nix): new entrypoint fragment `22-chromium-singleton.sh` sweeps stale `SingletonLock`/`SingletonCookie`/`SingletonSocket` on container boot — handles cross-container-restart cleanup that the mid-session DIMM-222 sweep can't catch
- feat(nix): new entrypoint fragment `30-gemini.sh` merges nix-staged MCP servers into `~/.gemini/settings.json` (mirrors the claude/codex/opencode pattern), with backup-before-merge, corrupt-file recovery, and stale-server eviction by `/opt/devcell/` prefix
- feat(nix): user-writable nix profile (`$HOME/.local/state/nix/profiles/profile`) replaces the read-only `/opt/devcell/.nix-profile` symlink — `nix profile add nixpkgs#htop` now succeeds from inside the cell; user-profile PATH precedes the stack profile so user installs override defaults while stack tools remain reachable
- feat(nix): `LD_LIBRARY_PATH` (35 KB, 546 paths, fragile under ARG_MAX) replaced by a merged `/opt/devcell/.nix-ld-libs/` directory pointed to by `NIX_LD_LIBRARY_PATH` and consulted only by the nix-ld shim — nix-built tools keep using RPATH untouched (closes the nixpkgs#327854 RPATH-override class of bugs)
- feat(scraping): new `devcell.scraping.extensions.<name>` registry — attrsOf submodule keyed by short name, each with `url`, `hash` (SRI), and `enable`; gives any cell a one-line opt-in to a third-party chromium extension without redefining the URL/hash pin
- feat(scraping): patchright MCP and the interactive chromium wrapper now fold enabled extensions into `--load-extension=<csv>` and `--disable-extensions-except=<csv>`; both flags omitted when no extension is enabled — no empty-arg surprises for Chromium's flag parser
- feat(scraping): per-extension derivation fetches the upstream zip at image build time via `pkgs.fetchurl` and unpacks via `runCommandLocal` — no zip vendored in the repo, store path content-addressed by SRI sha256 so two clean builds of the same commit produce byte-identical extension directories; manifest-at-root guard fails the build loudly with a diagnostic if upstream ever wraps everything in an inner dir
- feat(scraping): CapSolver v.1.17.0 registered with the official GitHub release URL + `sha256-luDxNlNoLYHWG3EHuqXTAxAzqkYnDpZt7eFFbqwRQT8=`, `enable = mkDefault false` — users opt in with `devcell.scraping.extensions.capsolver.enable = true` and configure the API key via the popup on first run (persistent `$HOME/.chrome` profile keeps the setting)
- feat(scraping): `nixhome/modules/scraping/default.nix` reworked end-to-end — patchright config / wrapper / interactive chromium wrapper share one `extensionArgs` source of truth
- feat(media): new `media.nix` module ships Plex MCP (`plex-mcp-server`) — Plex library/playlist/session/admin control over MCP
- feat(graphics): inkscape-mcp gains `INKS_WORKSPACE = "./"` so the agent reads and writes SVGs anywhere in the project tree (instead of being trapped in the default "inkspace" sandbox); `potrace` + `mkbitmap` added for bitmap → SVG tracing
- feat(llm): new `nixhome/modules/llm/gemini.nix` mirrors claude/codex/opencode (config staging, MCP entry, runtime settings.json merge); `default.nix` imports it
- feat(cfg): new `[cell].mac_address` TOML key — pinned NIC MAC for stable bot-protection identity across container restarts; honored on docker user-defined bridge (default), ignored on `--network=host`, vagrant wiring still TODO
- feat(cfg): new `[ports].publish_ip` TOML key — host interface docker publishes container ports on; default `"0.0.0.0"` (LAN-reachable regardless of dockerd bind default), override to `"127.0.0.1"` for loopback-only or a specific NIC IP; applies to VNC, RDP, and every forward entry
- feat(cfg): `[[volumes]]` entries now resolve `~` and absolute host paths uniformly so cross-cell shared mounts (`/Users/…/.devcell/PTC` etc.) behave the same on macOS and Linux
- feat(ci): new `pure-build` GHA matrix job builds + pushes per-arch per-stack pure images (base + ultimate × amd64 + arm64) to GHCR then mirrors to ECR Public; `cache-nix-action` keeps `/nix/store` warm between runs with a 5 GB GC cap; `pure-manifest` job stitches per-arch tags into multi-arch manifests
- feat(ci): impure buildx bake adds `oci-mediatypes=true` so dev-build images use OCI media types and play nicer with skopeo / nix2container loaders downstream
- chore(nix): `nixpkgs-edge` flake lock bumped from `591688422…` (2026-04-24) to `a4421cecfb7c…` (2026-06-02) — every `cell claude` session picks up claude-code 2.1.118 → 2.1.158 after the next image rebuild, 40 patch versions of upstream Claude Code fixes, features, and MCP improvements
- chore(nix): `nix2container` input added with its `nixpkgs` follow pinned to the main input — no separate nixpkgs evaluation
- chore(infra): `Taskfile.yml` +326 lines — `image:pure:*` tasks (build/push/mirror per-stack), `image:mirror` skopeo wrapper, `nix:validate` parse + attr-check, refreshed `image:` umbrella targets
- chore(infra): `Dockerfile` +52 lines — minor adjustments aligning with the new fragment layout and the pure-default flip
- chore(infra): `.nixhome-tmp/` staging tree created from a rename of `images/entrypoint.sh` plus a mirror of `nixhome/` — transitional layout that lets the impure docker context keep its own bind during the pure-default flip
- chore(deps): `.node-version` pinned at repo root; `go.mod` / `go.sum` updated for the new orchestration packages (heartbeat, df parser, prune planner)
- docs(scraping): `extensions.nix` header documents distribution policy (no vendored zips, no md5, no floating "latest" URLs) and bump workflow (`nix-prefetch-url` → `nix hash convert` → `task nix:validate`) — future bumps don't need to rediscover the playbook
- test(runner): `acquire_image_test.go` covers sequence walk, fallback fallthrough, build-failure surfacing, and joined chain errors; `preflight_test.go` covers Linux-OK, env-skip, Darwin-no-builder error contract
- test(runner): `launch_decision_test.go` rewritten as 6 table-driven cases covering every (HasNix × ExplicitBuild × LocalExists × DryRun) branch of the new sequence decision
- test(runner): new `df_test.go` (363 lines — ranks, dedup, JSON schema, kind filtering), `prune_test.go` (758 lines — every (GOOS, force, pure, rootless) combo), `humanbytes_test.go`, `build_debug_test.go`, `pure_build_test.go`, `pure_build_heartbeat_test.go`, `pure_nixhome_resolver_test.go`, `registry_test.go`, `tag_variants_test.go`
- test(serve): `responses_background_test.go` (407 lines) — background job lifecycle, polling, cancellation, error states; `responses_test.go` updated for the new handler signature with `*JobStore`
- test(cmd): new `build_df_test.go`, `build_stack_override_test.go`, `chrome_cookiedb_test.go`, `chrome_kick_mcp_test.go`, `chrome_paths_test.go`, `gemini_test.go`, `root_persistent_prerun_test.go`, `strip_test.go` — cover new build subcommands, gemini cmd, kick-mcp helper, cookie-db flush, root persistent pre-run reading project config
- test(integration): new `test/` package suite — `gui_test.go`, `image_nix_provenance_test.go`, `image_test.go`, `managed_mcp_staging_test.go`, `mise_node_install_test.go`, `mise_test.go` (586 lines), `playwright_singleton_lock_test.go`, `stealth_test.go`, `sudo_integration_test.go`, `sudo_test.go`, `usr_bin_env_test.go`, `variant_test.go` — covers image provenance via `/etc/devcell/.image-built-with-nix2container` marker, mise reshim short-circuit, playwright singleton recovery, stealth init.js regressions, sudo rights, `/usr/bin/env` shim, pure-vs-impure runtime variant equivalence
- test(rdp): `resolution_probe_test.go` (207 lines) — RDP resolution probing for dynamic screen sizing
- test(cfg): `cfg_test.go` (157 lines) — coverage for new `mac_address`, `publish_ip`, stack-override resolution
nix profile add works inside cells, browser state shares one profile across cell login + chromium + MCP, and RDP-into-cell works on first launch
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 join this conversation on GitHub.
Already have an account?
Sign in to comment
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.
Changes
cell <agent>default acquisition flips from "build whatever's needed" to "pull pure → pull impure → build pure → build impure" — fresh installs no longer wait through a multi-minute docker build whose output the orchestrator would discard on the next step;--impureopts back into the legacy Dockerfile path,--debianis a deprecated alias kept for one release,--pureis a silent no-op for back-compatcell builddefaults to the pure (nix2container) path; same flag set as agents (--impure,--debian,--pure) plus--updatefor flake refresh and--no-generateto skip build-context regencell build --stack <name>andDEVCELL_STACK=<name>env override[cell].stackfor a single build — precedence flag > env > TOML > "base" — no more editing.devcell.tomlto test a different stack imagecell build df— read-only Docker disk-usage table ranked by reclaimable bytes; rows tag pinned-by-running-container vs reclaimable, surface per-row reclaim commands as hints, never execute them;--top/--all/--kind/--jsonfor narrowing and scriptingcell build prune— composes an ordered cleanup plan for the current host (GOOS), docker vs nix path, and rootless-aware on Linux; default sequence is conservative,--forceopts into nuclear cleanup (Docker Desktop wipe / qcow nuke / aggressive nix GC); confirms before executingcell geminiagent alongside claude/codex/opencode — same flag-stripping shell, same per-agent config layout; new--nix-daemonflag (cross-agent) keeps the in-container nix-daemon running so users cannix profile installad-hoc fixes from a live cellcell loginflushes the Chrome Cookies sqlite WAL between phase 1 (interactive login) and phase 2 (CDP extraction) so phase 2 reads a consistent snapshot, warns the user when the Cookies mtime is older than the session start ("did you actually log in?"), and SIGTERMsmcp-server-patchrightin every running cell sharing the same cell-home bind mount so Claude/etc. pick up the fresh storage-state.json on the next browser tool call — no more stale-cookie surprises after relogcell loginwrites storage state to$HOME/.playwright/storage-state.json(DIMM-208 layout) and pre-creates the parent dir so first-run can't race[cell].stack/modules/per_session_imageinto runner globals BEFORE any subcommand's RunE —cell buildnow tags images with the project's actual stack instead of alwaysdevcell-user:base-purecell servePOST/v1/responsesaccepts"background": true→ returns 202 + job stub; clients poll GET/v1/responses/{id}until status is terminal (completed/failed/cancelled); in-memoryJobStoretracks lifecycle and exposes aCancelFuncper job for future POST/v1/responses/{id}/cancelcell servePOST/v1/responsesaccepts"stream": truefor the claude agent and returns Server-Sent Events with token-level deltas (opencode still buffers); combiningstream + backgroundreturns 400 withunsupported_combination— clients pick one mode/v1/responses/{id}handler — sameResponsesObjectshape whether the underlying job is still in-progress (empty Output) or terminal (fully populated, identical to a sync POST response)AcquireImageorchestrator walks the action sequence fromDecideLaunchActionsPure, invokes one closure per effective action (PullPure/PullImpure/BuildPure/BuildImpure), stops at first success, and surfaceserrors.Join-ed chain on total failure — every fallback attempted is visible to the user, no silent revertsDecideLaunchActionsPurereturns an ordered sequence (replaces the singularDecideLaunchActionPure) and models fallback as data (ActionPullPure/ActionPullImpure/ActionBuildPure/ActionBuildImpure/ActionUseLocal/ActionDryRun) so the launcher's decision logic is unit-testable without invoking docker or nixPullAndTagImpuredual-tags an impure pull as bothUserImageTag()andUserImageTagPure()— a subsequent default-path launch finds the image locally without retrying the pull chainUserImageTagPure()(devcell-user:<stack>-pure),StackImageTagPure(stack),StackImageTagImpure(stack)registry tags; deprecatedStackImageTagDebianaliases impure for one release;PickImageTag(impure bool)is the single seam every runtime caller uses to choose a local image variantPreflightNixBuilder(+PreflightNixBuilderFromProbetestable seam) extracted from the BuildImagePure body — callers probe host nix usability before deciding to attempt a pure build; pull-only cold starts on Mac no longer trigger the linux-builder gatepure_build.go) invokesnix2container'scopyToDockerDaemonvia skopeo'snix:transport; supports both local-flake (path:) and remote-flake (github:) refs; tees progress to a heartbeat goroutine; no docker-build fallback inside the pure path (failures surface, do not silently revert)path:flake from the project tree, falls back to the upstreamgithub:DimmKirr/devcell?dir=nixhomeflake when no local nixhome exists (mirrors scaffold's Dockerfile FROM-image fallback)df.go+df_collect.go) —docker system df -vJSON parsed into ranked entries, image dedup via unique-blob accounting, pin-count via running-container join; emits stable JSON schema for scriptingRunDockerPrune/RunNixPruneafter confirmation; rootless detected viadocker infoat the call sitecell build --debugtees output to a file and a ring buffer for post-mortem artifact capturemise reshim+ recursive chowns over the persistent~/.local/share/mise(~17k entries) when~/.tool-versionsis unchanged — drops macOS launches from ~57s to ~10smcp-server-patchrightin every running cell that shares the current cell-home bind mount; best-effort, never errors out the calling commandSyncNixhomerunsgit init+git add .inside the synced copy so nix's flake source filter (which uses git ls-files) sees every file — fixes the case where dest lives inside a gitignored dir (.devcell/) and nix would otherwise see an empty tree.devcell.toml/.devcell/no longer eagerly invokesbuildImageWithSpinner— image acquisition is owned by the unified orchestrator below itnixhome/packages/image.nix(911 lines) — nix2container OCI image builder; content-addressed; bakes home-manageractivationPackageinto the image filesystem without running activation at runtime; per-stack outputs keyed on HOST system (not target) so IFD helpers run on the right arch; optionalincludeNix(default on) keeps the runtime-installable nix profile capabilitynix2containerflake input added;skopeo-nix2containeroverlay forces unstable's skopeo 1.21+ for thenix:transport while keepingnix2container-binon the main nixpkgs pin (avoids a 0-byte closure-graph.json regression from a wholesale pkgs swap)lib.mkHomenow threadsselfintoextraSpecialArgsso downstream modules can resolve the flake source path for content-addressed stagingnixhome/entrypoint.shfor the pure-image runtime — reads/etc/devcell/metadata.jsonfor stack/module/package counts and prefers runner-injectedDEVCELL_BUILD_DATE/DEVCELL_BUILD_REV(sourced from OCI labels) so post-2026-05-16 cells display real provenance instead of placeholders; pre-2026 image fall-through preservedbase.nixexportsNIX_LDpointing at the real nix glibc loader (ld-linux-aarch64.so.1orld-linux-x86-64.so.2per arch) so non-nix binaries (mise-installed node/go, pip wheels, downloaded gpg keychains) load against a consistent loader on both pure and impure imagesbase.nixskips the impurestageEntrypointsrsync activation when/etc/devcell/.image-built-with-nix2containermarker is present — pure images already have fragments staged at build time04-nix-daemon.shboots a per-cellnix-daemonwhen the agent flag--nix-daemonis passed, enabling runtimenix profile install/home-manager switchfor ad-hoc fixes22-chromium-singleton.shsweeps staleSingletonLock/SingletonCookie/SingletonSocketon container boot — handles cross-container-restart cleanup that the mid-session DIMM-222 sweep can't catch30-gemini.shmerges nix-staged MCP servers into~/.gemini/settings.json(mirrors the claude/codex/opencode pattern), with backup-before-merge, corrupt-file recovery, and stale-server eviction by/opt/devcell/prefix$HOME/.local/state/nix/profiles/profile) replaces the read-only/opt/devcell/.nix-profilesymlink —nix profile add nixpkgs#htopnow succeeds from inside the cell; user-profile PATH precedes the stack profile so user installs override defaults while stack tools remain reachableLD_LIBRARY_PATH(35 KB, 546 paths, fragile under ARG_MAX) replaced by a merged/opt/devcell/.nix-ld-libs/directory pointed to byNIX_LD_LIBRARY_PATHand consulted only by the nix-ld shim — nix-built tools keep using RPATH untouched (closes the nixpkgs#327854 RPATH-override class of bugs)devcell.scraping.extensions.<name>registry — attrsOf submodule keyed by short name, each withurl,hash(SRI), andenable; gives any cell a one-line opt-in to a third-party chromium extension without redefining the URL/hash pin--load-extension=<csv>and--disable-extensions-except=<csv>; both flags omitted when no extension is enabled — no empty-arg surprises for Chromium's flag parserpkgs.fetchurland unpacks viarunCommandLocal— no zip vendored in the repo, store path content-addressed by SRI sha256 so two clean builds of the same commit produce byte-identical extension directories; manifest-at-root guard fails the build loudly with a diagnostic if upstream ever wraps everything in an inner dirsha256-luDxNlNoLYHWG3EHuqXTAxAzqkYnDpZt7eFFbqwRQT8=,enable = mkDefault false— users opt in withdevcell.scraping.extensions.capsolver.enable = trueand configure the API key via the popup on first run (persistent$HOME/.chromeprofile keeps the setting)nixhome/modules/scraping/default.nixreworked end-to-end — patchright config / wrapper / interactive chromium wrapper share oneextensionArgssource of truthmedia.nixmodule ships Plex MCP (plex-mcp-server) — Plex library/playlist/session/admin control over MCPINKS_WORKSPACE = "./"so the agent reads and writes SVGs anywhere in the project tree (instead of being trapped in the default "inkspace" sandbox);potrace+mkbitmapadded for bitmap → SVG tracingnixhome/modules/llm/gemini.nixmirrors claude/codex/opencode (config staging, MCP entry, runtime settings.json merge);default.niximports it[cell].mac_addressTOML key — pinned NIC MAC for stable bot-protection identity across container restarts; honored on docker user-defined bridge (default), ignored on--network=host, vagrant wiring still TODO[ports].publish_ipTOML key — host interface docker publishes container ports on; default"0.0.0.0"(LAN-reachable regardless of dockerd bind default), override to"127.0.0.1"for loopback-only or a specific NIC IP; applies to VNC, RDP, and every forward entry[[volumes]]entries now resolve~and absolute host paths uniformly so cross-cell shared mounts (/Users/…/.devcell/PTCetc.) behave the same on macOS and Linuxpure-buildGHA matrix job builds + pushes per-arch per-stack pure images (base + ultimate × amd64 + arm64) to GHCR then mirrors to ECR Public;cache-nix-actionkeeps/nix/storewarm between runs with a 5 GB GC cap;pure-manifestjob stitches per-arch tags into multi-arch manifestsoci-mediatypes=trueso dev-build images use OCI media types and play nicer with skopeo / nix2container loaders downstreamnixpkgs-edgeflake lock bumped from591688422…(2026-04-24) toa4421cecfb7c…(2026-06-02) — everycell claudesession picks up claude-code 2.1.118 → 2.1.158 after the next image rebuild, 40 patch versions of upstream Claude Code fixes, features, and MCP improvementsnix2containerinput added with itsnixpkgsfollow pinned to the main input — no separate nixpkgs evaluationTaskfile.yml+326 lines —image:pure:*tasks (build/push/mirror per-stack),image:mirrorskopeo wrapper,nix:validateparse + attr-check, refreshedimage:umbrella targetsDockerfile+52 lines — minor adjustments aligning with the new fragment layout and the pure-default flip.nixhome-tmp/staging tree created from a rename ofimages/entrypoint.shplus a mirror ofnixhome/— transitional layout that lets the impure docker context keep its own bind during the pure-default flip.node-versionpinned at repo root;go.mod/go.sumupdated for the new orchestration packages (heartbeat, df parser, prune planner)extensions.nixheader documents distribution policy (no vendored zips, no md5, no floating "latest" URLs) and bump workflow (nix-prefetch-url→nix hash convert→task nix:validate) — future bumps don't need to rediscover the playbookacquire_image_test.gocovers sequence walk, fallback fallthrough, build-failure surfacing, and joined chain errors;preflight_test.gocovers Linux-OK, env-skip, Darwin-no-builder error contractlaunch_decision_test.gorewritten as 6 table-driven cases covering every (HasNix × ExplicitBuild × LocalExists × DryRun) branch of the new sequence decisiondf_test.go(363 lines — ranks, dedup, JSON schema, kind filtering),prune_test.go(758 lines — every (GOOS, force, pure, rootless) combo),humanbytes_test.go,build_debug_test.go,pure_build_test.go,pure_build_heartbeat_test.go,pure_nixhome_resolver_test.go,registry_test.go,tag_variants_test.goresponses_background_test.go(407 lines) — background job lifecycle, polling, cancellation, error states;responses_test.goupdated for the new handler signature with*JobStorebuild_df_test.go,build_stack_override_test.go,chrome_cookiedb_test.go,chrome_kick_mcp_test.go,chrome_paths_test.go,gemini_test.go,root_persistent_prerun_test.go,strip_test.go— cover new build subcommands, gemini cmd, kick-mcp helper, cookie-db flush, root persistent pre-run reading project configtest/package suite —gui_test.go,image_nix_provenance_test.go,image_test.go,managed_mcp_staging_test.go,mise_node_install_test.go,mise_test.go(586 lines),playwright_singleton_lock_test.go,stealth_test.go,sudo_integration_test.go,sudo_test.go,usr_bin_env_test.go,variant_test.go— covers image provenance via/etc/devcell/.image-built-with-nix2containermarker, mise reshim short-circuit, playwright singleton recovery, stealth init.js regressions, sudo rights,/usr/bin/envshim, pure-vs-impure runtime variant equivalenceresolution_probe_test.go(207 lines) — RDP resolution probing for dynamic screen sizingcfg_test.go(157 lines) — coverage for newmac_address,publish_ip, stack-override resolution