Skip to content

Develop devlaunch inside its own devcontainer, on an isolated Docker daemon #93

Description

@blooop

Destination

dl blooop/devlaunch@<branch> opens a devcontainer that carries its own Docker
daemon
, and inside that container both pixi run test-e2e and dl itself work
against that daemon. Several branches can then be developed and e2e-tested
concurrently on one host without touching the host's Docker, the host's devpod
workspace list, or each other's.

Notes

Domain. devlaunch drives devpod; devpod's docker provider drives a Docker
daemon. Nesting devlaunch inside a devcontainer therefore means nesting a Docker
daemon, which is the whole of the problem. dl itself contains no Docker code —
grep -rn docker devlaunch/*.py is empty — so every question here is about the
container's shape, not about devlaunch's source.

Prior art, and why this map exists. Two PRs are already open and stuck:

This map is chartered from the host, where that verification is finally possible:
Docker 29.5.2, devpod v0.26.1, docker provider initialised. The map's first job
is to close #69's open claim; its second is to go past #69's scope — #69 only ever
claimed pytest runs in there, never that dl does.

Also in the tree, dormant. test/docker/{Dockerfile.dind,docker-compose.test.yml,entrypoint.sh}
is a standalone alpine dind harness that runs the same e2e suite. CI does not
invoke it (.github/workflows/ci.yml has no dind job). Two harnesses for one job
is a principle-2 question, ticketed below.

Known interaction — recreate/restart fail on a relative initializeCommand.
This devcontainer's initializeCommand is the relative path
.devcontainer/claude-code/init-host.sh, and devpod up . --recreate is exactly
the command every prototype here needs. If that map's bug bites, it bites this
map's prototypes first.
It did not bite. The daemon prototype ran both
--recreate and stop+up against exactly that relative hook, on devpod
v0.26.1 with a local-folder source, from a neutral cwd — all fine. That narrows
#87 rather than closing it, and the datum has been posted there. Not this map's
bug to fix either way.

The devpod being run is a fork. The shipped v0.26.1 binary is
skevetter/devpod, not loft-sh/devpod — verified from the binary's symbol
table. That resolves the provenance puzzle #70 flagged (loft-sh publishes no v0.2x
tag; its newest release is v0.7.0-alpha.34), and it means the upstream to read,
patch or file against is the fork. Two findings on this map were reached by reading
devpod internals; both were read against the fork. Posted on #70.

Extra principle for this effort (applies after the standard four). Verify on
metal.
Every claim about the container's shape is settled by building the thing
and looking, never by reading a feature's README. This map exists because that
step was skipped once already.

And it binds the review stage, not only the build. Added after the ssh-mount
ticket, where a reviewer's second pass reversed a correct finding — asserting
from reading that touch "only bumps mtime", where the pass that ran it had
measured EROFS against the read-only mount. The two passes differed in exactly
one thing: whether the command was run. A review that only reads is capable of
un-finding what a review that runs has found, so a reviewer overturning a
measured claim must measure it too.

Process rule for this map's build tickets. A review step that mutates code —
mutation testing, sabotage commits, deliberate breakage — runs in its own git
worktree
. On the purge ticket a reviewing subagent mutated the shared worktree
while the build agent was committing, and a commit swept the live mutation up and
deleted a guard from the branch. It surfaced only because the subagent volunteered
it.

Merge order (autonomous run stopped here). Three PRs are approved and waiting
on a human: #106 (e2e namespace) → #118 (the two e2e defects, held behind
#106 by its own ticket's stated precondition) → #119 (credential warnings,
independent). Four tickets unblock on those merges: #107, #109, #114, #116. #117
is blocked on #109 by a devcontainer.json file conflict, not by logic. #120 is
parked with a handoff — actionable, but worth more after the queue drains.

Standing constraint. Work happens in a worktree; the primary checkout at
/home/ags/projects/devlaunch is in concurrent use by other agents and its HEAD
must not move.

Decisions so far

  • What actually conflicts between two concurrent devlaunch efforts on one host?
    — the three "isolation mechanisms" answer three disjoint questions, and only one
    is about concurrency. Per-branch workspaces stay (sole cover for 5 collisions;
    every name derives from (owner, repo, ref) alone, so it is independent of XDG).
    Scratch XDG dirs stop counting as isolation — strictly subsumed by a container's
    own $HOME; the CLAUDE.md paragraph survives as a blast-radius guard, not a
    mechanism. The nested daemon's justification is capability, not concurrency:
    dl's only subprocess is devpod (dl.py:781), which needs a daemon, so dl
    cannot run in a daemon-less container at all. Three genuine concurrency collisions
    remain — host netns, hardcoded e2e workspace ids, dl --purge's blast radius —
    each with a fix an order of magnitude smaller than a daemon. (agent)

  • Does dockerd actually come up under the docker-in-docker feature?
    yes, cold, first try, no intervention. Give the devcontainer its own Docker daemon for e2e testing #69's load-bearing claim holds. The
    inner daemon is separate on every axis (own ID, ServerVersion=29.7.1-1 vs the
    host's 29.5.2, Images=0, own docker0 at 172.18.0.1/16 in its own netns,
    /var/lib/docker on a per-devcontainer named volume) and the host is
    byte-identical before and after. Crucially StorageDriver=overlayfs, not the
    vfs fallback — the feature's volume mount earns that, so nested e2e runs at
    normal speed. One of Give the devcontainer its own Docker daemon for e2e testing #69's sub-claims is refuted: the feature declares no
    init key at all (HostConfig.Init is nil); PID 1 is devpod agent container daemon and dockerd is its reparented orphan. Give the devcontainer its own Docker daemon for e2e testing #69's conclusion survives, its
    comment text does not. (agent)

  • Does dropping --network=host cost anything?
    no; it is safe, and it removes a collision class. Every checklist item
    passed in a runArgs-free build: SSH agent, ~/.config/gh, prefix.dev,
    api.github.com, claude --version, and two levels in the inner daemon cloned
    devlaunch in 5.6s through double NAT. grep -rn "localhost\|127.0.0.1\|host.docker.internal"
    over devlaunch/ test/ pyproject.toml .github/zero hits. The
    OAuth contradiction resolves against the README: its mechanism is real
    (the callback is a loopback listener on 54545; a host curl to the
    container's loopback gives exit 7) but its conclusion is not — devpod ssh -L 54545:localhost:54545 restores it, and the point is moot anyway because the
    feature bind-mounts ~/.claude, so credentials arrive already populated and
    the in-container flow never runs. .devcontainer/claude-code/README.md is
    the wrong document; Give the devcontainer its own Docker daemon for e2e testing #69 needs no correction.
    Counter-evidence for keeping the
    flag, demonstrated: with host networking a container's nc -l -p 54545 hits
    Address in use against the host's listener — What actually conflicts between two concurrent devlaunch efforts on one host? #95's port-collision half,
    live. (agent)

  • Can devpod up — and dl itself — run from inside the dind devcontainer?
    yes, all five steps. Provider add 0.46s; nested devpod up 25.2s cold with
    devpod ssh answering from the third level down; the e2e suite genuinely runs
    (7 passed, 1 skipped, 39s); dl <repo> from inside completes clone → id → up
    → ssh → exit 0 in 7.9s; and what it creates is invisible to the host's devpod list and docker ps. The destination is reachable. Three caveats, each
    ticketed: two defects break pixi run test-e2e before dind is involved and
    reproduce on the host; isolation leaks through ~/.ssh/config; and the
    scratch-XDG habit CLAUDE.md teaches silently destroys GH_TOKEN forwarding
    inside the container. Also: dl --purge inside the container ate an unrelated
    nested workspace — which is the strongest concrete argument this map has for
    the nested daemon, since on the host that would have been real work. (agent)

  • One dind harness or two? test/docker/ vs the devcontainer feature
    it goes. One harness, and it is the devcontainer. The standalone alpine
    harness's own header names exactly one job, which is verbatim the devcontainer's
    job and now measured. The one serious case for keeping it — a CI runner that
    cannot nest — collapses: ubuntu-latest is an ephemeral VM with Docker
    preinstalled, so there is no host to protect and the harness's whole purpose is
    free there. It is also the wrong image regardless: alpine/musl + pip install -e

    • curl …/releases/latest diverges from the shipped Ubuntu+pixi environment on
      every axis under test, and reaches around the lockfile that the devpod pin exists
      to enforce. Deletion folded into the landing ticket, because Give the devcontainer its own Docker daemon for e2e testing #69's diff carries
      three test/docker/ references and splitting them re-imports a dangling pointer
      either way round. (agent)
  • Do credentials reach a workspace nested two levels deep?
    they do; the instruction this repo teaches is what destroys them, and it does
    so on the host as well.
    gh arrives via the ~/.config/gh bind mount and dl
    forwards it onward as GH_TOKEN. But the scratch-XDG_CONFIG_HOME habit in
    CLAUDE.md/dev.sh guards less than it costs — while killing gh outright.
    Partly corrected by PR Stop silently launching unauthenticated workspaces #119's reviewer, on metal: the "guards nothing" half
    is wrong. config.toml can pin repos_dir to an absolute path that beats
    XDG_CACHE_HOME outright (worktree/config.py:78), so with only the cache
    scoped, repos_dir resolved to the real ~/.cache/devlaunch/repos — the
    precise "costs someone their workspace list" hazard. The repo already recorded
    this at test/conftest.py:47-49 and the resolving ticket missed it. The
    deletion still stands
    (the gh breakage is worse, and it is unconditional
    while the repos_dir hazard needs a config.toml that does not exist on this
    host), but it is a trade-off, not a free removal, and the prose must say so. Verified on the host, read-only:
    XDG_CONFIG_HOME=/tmp/… gh auth token → rc=1; unset → rc=0. So every dl-next
    run this repo has taught has been launching unauthenticated workspaces; the
    container only made it visible. Compounding it, the failure is unreachable,
    not merely quiet: dl.py:75 pins basicConfig(level=INFO) with no override, so
    gh_auth's debug lines can never be seen by anyone. Nothing is broken two levels
    down and no second credential path is needed — the chain is deliberate and
    tested. Fix is delete-the-passenger plus promote-to-warning, ticketed as
    Stop silently launching unauthenticated workspaces. (agent)

  • What does the nested daemon cost in disk and time?
    no mitigation; document the number. ~4.2 GB per actively-used branch
    (1.9 GB host-side + 2.3 GB inner), ~12.6 GB for three concurrent, against
    87 GB free on a host already holding ~98 GB of already-reclaimable garbage.
    Both candidate mechanisms cost ~an order of magnitude more than they save, and
    docker image prune in a task is actively harmful — it prunes the inner
    daemon's images, the ones the next e2e run needs, turning every warm 7.9s dl
    into a cold 25.2s one. But orphan accumulation is real, and it corrects Can devpod up — and dl itself — run from inside the dind devcontainer? #96's
    implication
    : devpod's DeleteDevContainer is a plain docker rm without
    -v, and its DeleteVolume has zero callers — dead code. Metal: 14 local
    volumes, 2 active, ~9.9 GB dead vs 1.0 GB live. Can devpod up — and dl itself — run from inside the dind devcontainer? #96's totals returned to
    baseline because its agent removed the volumes by hand, not because
    devpod delete worked. The leak predates dind (all twelve orphans are plain
    -pixi volumes, some from July) so dind enlarges rather than causes it — but it
    is unbounded, being per-branch-ever-opened rather than per-live-branch. Posted
    as a datum to Build: persist the devpod workspace id instead of re-deriving it, and reconcile the orphans #88, whose reconciler is its natural owner. (agent)

  • Which devlaunch build runs inside the devcontainer?
    pixi run dl, and nothing else. The container already installs the
    checkout editable, so ./dev.sh's job is done before you arrive. The forcing constraint is devpod, not naming: inside the
    container devpod exists only in the project env, so a devlaunch installed
    anywhere else would die on the lookup.
    Refuted on metal by PR Say which build to run inside the devcontainer #125:
    command -v devpod inside answers /usr/local/bin/devpod, the working agent
    binary devpod injects into every workspace it creates. The recommendation
    stands on a better argument — entry path, not absence: that binary exists
    because devpod created this container, so opening the same devcontainer via
    VS Code or plain devcontainer up injects nothing, and a devlaunch outside the
    project env then works on one route in and fails on the other. Harder to
    diagnose than never working. pixi run is present on both routes and pinned.
    Note also the two binaries match in version here only because this host runs
    the pinned version
    , and both read the same ~/.devpod — so this is not an
    isolation boundary. ./dev.sh exits at its first check in there because uv is absent —
    and that refusal is correct, since a -next build would be a second editable
    install of the same tree printing the same provenance string, destroying the one
    property the two-install convention rests on. The host's fallback argument does
    not carry over: on the host dl is the way in; inside, you are already in, and
    git stash restores a working pixi run dl with no reinstall. (agent)

  • Nested devpod writes broken ProxyCommand entries into the host's real ~/.ssh/config
    narrow the mount. Stop bind-mounting the host's ~/.ssh directory; mount
    only agent.sock and a read-only known_hosts, leaving /home/vscode/.ssh
    container-local so devpod's blocks die with the container. Proved on metal that
    the private key is not load-bearing (socket alone gives ssh -T git@github.com
    → "Hi blooop!"), that known_hosts is, and that the "signing key" leg does
    not currently exist at all. The leak is only on abandonmentcmd delete
    unconditionally calls ssh.RemoveFromConfig — but two riders stop that being
    reassuring: writeSSHConfig is a whole-file os.WriteFile guarded by a
    process-local mutex, so the container rewrites the user's entire config
    (personal Host blocks included) with no cross-process locking; and
    transformHostSection normalises as it goes, so a lifecycle is no-net-change
    modulo normalisation, not a no-op. Also: dl never uses the <ws>.devpod
    aliases — it shells out to devpod ssh <id> (dl.py:989) — so these blocks are
    unused by devlaunch entirely. (agent)

  • No init process in the dind devcontainer: do zombies accumulate?
    no leak; no change warranted. Six e2e iterations in one long-lived
    container: zombies 0 throughout, and PIDs and children-of-PID-1 return to
    the same integers (10 and 1) after every iteration — not merely bounded.
    The mechanism was confirmed rather than inferred: strace from the host
    namespace (impossible from inside — Yama blocks seizing a PID namespace's own
    init) shows devpod's agent running wait4(-1, …, WNOHANG) and draining to 0,
    which are exactly the two things a broken init gets wrong since SIGCHLD is not
    queued. SigIgn: 0 rules out the SA_NOCLDWAIT shortcut. Burst-tested with
    2000 simultaneous orphans: all collected in ~1s. Does dockerd actually come up under the docker-in-docker feature? #94's concern was correctly
    raised and is unfounded — the feature declares no init key, but devpod's
    agent is already an init, deliberately
    , so "init": true would add a PID 1
    doing a job already done. (agent)

  • Document that pixi run dl is the only build inside the devcontainer
    built and shipped in 0.0.17 (#125).
    The two-install convention in AGENTS.md stopped at the host's edge; it now says
    what to run once you are inside. Its reviewer is what refuted the "devpod exists
    only in the project env" half of Which devlaunch build runs inside the devcontainer: dl, pixi run dl, or dl-next? #99/usr/local/bin/devpod is injected by
    devpod into every workspace it creates — so the recommendation now rests on the
    entry-path argument instead. (agent)

  • list_workspaces reports empty when it merely failed to read
    built and shipped in 0.0.18 (#122).
    A failed or unparseable devpod list collapsed to [], which is the same value
    as "no workspaces" — so a dl that could not see the list behaved as though the
    list were empty. Now raises UnreadableWorkspaceList. Principle 3: the empty
    list and the unreadable list were one representation doing two jobs. This is the
    blocker that had to clear before the purge blast-radius ticket could be safe.
    (agent)

  • Run the e2e suite in CI
    built and shipped in 0.0.18 (#123).
    Plain unnested pytest -m e2e on ubuntu-latest, per the corrected premise
    recorded above. It also closes the fog patch below about a suite that cannot
    distinguish "opted out" from "could not run": -ra is load-bearing, so a run
    now reports its skip reasons rather than presenting a registry outage as a green
    tick over zero containers. (agent)

  • Land the dind devcontainer and correct the docs that contradict it
    built and shipped in 0.0.18 (#124).
    The map's destination, landed: the docker-in-docker feature enabled, --network=host
    dropped, test/docker/ deleted per One dind harness or two? test/docker/ vs the devcontainer feature #98, and dev-add-docker wired into
    postCreateCommand so a fresh container has a provider without a manual step.
    Supersedes #69, whose
    load-bearing claim this map verified rather than assumed. (agent)

  • Mount only the ssh files that are load-bearing, not the whole ~/.ssh
    built and merged, unreleased (#130).
    The Nested devpod writes broken ProxyCommand entries into the host's real ~/.ssh/config #112 decision, landed: two files instead of the directory, so
    /home/vscode/.ssh is container-local and the Host <id>.devpod blocks a
    nested devpod writes cannot outlive the container whose ProxyCommand they
    name. The leak is not prevented, it is unrepresentable — principle 3, which is
    why the fix is a mount list rather than a cleanup step. Verified both ways on
    metal: the defect reproduced against a directory mount pointed at a stand-in
    ssh directory (stanza written through, and surviving the container's
    destruction), then the fix on the developer's real config (agent forwarding
    live, git ls-remote to GitHub at exit 0, config byte-identical).
    Two findings the ticket could not have known. This devcontainer could not be
    built from a checkout at all
    : the image takes the repository root as its
    build context and copies nothing out of it, and .pixi holds a symlink the
    context hasher refuses outright, so devpod up . failed before it started on
    any checkout with an installed environment. Pre-existing, invisible until
    something tried to build the devcontainer from the checkout; a .dockerignore
    fixes it. And making known_hosts present and making it read-only are the
    same change, and together they broke the hook that makes it present
    — a bare
    touch was the initializeCommand's last command, so its exit status was the
    hook's, and inside this container that file is the read-only mount. A non-zero
    initializeCommand aborts devpod up outright, so the first version of this
    change was self-defeating in exactly the environment this map exists to reach.
    Caught in review; and the reviewer's own later pass reversed the finding and
    was wrong
    , the only difference being that the first had run the command. So
    verify on metal belongs to the review stage too, not only to building.
    (agent)

  • dl --purge deletes workspaces devlaunch never created
    built and merged (#129),
    closing the third and last of What actually conflicts between two concurrent devlaunch efforts on one host? #95's genuine concurrency collisions. "Mine" is
    defined as the workspace's source being a clone under devlaunch's cache
    not metadata.json, which the ticket had proposed. Both predicates agree
    exactly on the six live workspaces here, so principle 1 broke the tie on failure
    modes: metadata.json lives inside the directory a purge removes and
    purge_all_data only warns on a failed devpod delete, so one failed delete
    plus one successful purge leaves a workspace permanently unrecognisable. It is
    also append-mostly — 39 records for 6 live workspaces. The cache predicate states
    the real invariant: a purge removes the cache, so what it deletes is what that
    cache backs. Proved read-only on the host that it would have spared
    pythontemplate — the exact workspace The e2e suite can delete a developer's real devpod workspaces #103 measured a purge would have eaten —
    then settled in CI's e2e job with two real workspaces, one deleted and one left
    standing and named. Graduated
    #131 on the way: purge exits 1
    and leaves the whole cache when a container wrote into a clone as another uid,
    which is squarely this map's business since nesting is exactly where uids stop
    agreeing. (agent)

  • Workspace.source_type is a tag beside a parallel field, and its sentinel arm is dropped silently
    built and shipped in 0.0.19 (#132).
    source_type: str + source: str becomes
    LocalFolder(path) | GitRepository(url) | UnrecognisedSource(payload). Gone:
    a kind and a value that disagree (no tag left to be wrong), str(dict) in a
    field typed as a path, and a reader that answers some arms and drops the rest —
    ty turns that last one into a build failure. Deliberately still
    representable: GitRepository("/home/dev/p"), because devpod up <bare-repo>
    really does record path-shaped git sources; what is gone is the disagreement.
    The premise was verified rather than assumed — exhaustiveness passes, deleting
    an arm fails, and adding a fourth arm fails at every previously-complete reader
    (35 diagnostics when Workspace changed) — with one limit found and pinned by a
    test: the parser is a producer, and a producer that stops producing an arm
    type-checks fine. The unreadable arm is real: devpod's own source struct carries
    image and container, read from the shipped binary. dl --purge deletes workspaces devlaunch never created #107's purge guard carried
    through with its mutation battery still dead (17/17). (agent)

Not yet specified

  • What the CI story isgraduated into Run the e2e suite in CI.
    The premise it was written under turned out to be wrong: a runner and a dev box
    are not two versions of one problem. The dev box needs a nested daemon because
    it is shared and long-lived; a runner is ephemeral and needs nothing, so the CI
    job is plain unnested pytest -m e2e.
  • Whether the pixi run dev* tasks in pyproject.toml, which shell out to
    devpod up . directly, survive or are superseded by dl itself. The conflict
    ticket found these half-broken already: devpod up . names the pixi volume from
    the checkout directory basename, so this host carries main-pixi, t1-pixi,
    w1-pixi — and any two repos with a main worktree share one.
  • Whether devlaunch should ever set DEVPOD_HOME. devpod's workspace namespace
    lives in ~/.devpod, not under XDG, and devlaunch never sets it
    (grep -rn DEVPOD_HOME . is empty) — so scratch XDG dirs do not scope
    devpod list. A container gets its own $HOME and thus moots this for the
    in-container story; it stays open for anything run on the host. Also
    unresolved: whether devpod writes its ~/.ssh/config blocks relative to
    $HOME or to DEVPOD_HOME.
    Answered on metal by the e2e blast-radius
    build ticket: neitherdevpod up writes its Host <id>.devpod block to
    the real ~/.ssh/config regardless of DEVPOD_HOME, unless DEVPOD_SSH_CONFIG
    overrides it. PR Give the test suite a devpod namespace of its own #106's reviewer established that DEVPOD_SSH_CONFIG is
    subcommand-scoped, so devpod ssh/delete still resolve the real file.

    Corrected by the ssh-leak ticket, from source: the flag's absence on delete
    is not the whole story — the path is persisted as sshConfigPath
    (pkg/provider/workspace.go:73, omitempty) and delete resolves from the
    persisted value
    . The practical consequence is unchanged for existing
    workspaces, whose three workspace.json files on this host carry no
    sshConfigPath key and therefore resolve to the real file. So scoping devpod
    takes two variables, not one, and only takes effect for workspaces created
    after it is set. Found
    independently by two agents
    on unrelated tickets (the e2e blast-radius build
    and the networking grilling), which is as close to corroboration as this map
    gets. Consequence worth carrying: scratch-DEVPOD_HOME runs are less
    contained than they look
    , and every instruction in this repo that recommends
    one is incomplete.
  • Does the inner /var/lib/docker plateau or creep? Plateau, settled by
    the zombie prototype as a side effect: across six consecutive e2e iterations
    docker system df never moved off Images 1.254GB / Containers 0B / Build Cache 0B, du -sm = 1198 MB. So ~4 GB per branch is a budget, not a floor,
    and the no-mitigation call on cost stands on firmer ground than when it was made.
  • The e2e suite cannot distinguish "opted out" from "could not run"
    closed by Run the e2e suite in CI,
    which made -ra load-bearing so skip reasons are reported. The record of why it
    mattered: a run
    with a slow registry reported 7 passed, 14 skipped having created zero
    containers — byte-identical summary to a healthy run. Only 1 of those 14 is a
    real defect (Two defects break pixi run test-e2e before dind is involved #111's purge test); the other 13 are a declared opt-out. Fed to the
    CI ticket, which is where a silently-green job actually costs something.
  • ./dev.sh's refusal inside the container is incidental — it rests entirely
    on uv being absent. The day uv is added for an unrelated reason, dev.sh
    silently starts succeeding in there and produces two indistinguishable editable
    installs of the same tree. A one-line intentional guard would fix it; whether
    that is worth adding before the silent flip happens is undecided.
  • devlaunch has two different config dirs: worktree/config.py:89 honours
    XDG_CONFIG_HOME to read, while completion.py:33 hardcodes
    ~/.config/devlaunch/ to write. Surfaced as the evidence that the scratch-config
    advice was bogus; the inconsistency itself is untouched.
  • Five debug log sites nothing can turn ongh_auth ×5 plus run_devpod's
    Running: %s (dl.py:782) — because dl has no --verbose/-q and
    basicConfig(level=INFO) is pinned. Stop silently launching unauthenticated workspaces #115 deliberately does not depend on fixing
    this; whether dl should grow a verbosity flag at all is undecided.
  • metadata.json has no locking of any kind — whole-file load at
    storage.py:69-74, whole-file save at :247-251 — so two concurrent dl runs
    lose updates. A container fixes it incidentally by giving each run its own file;
    the underlying defect is untouched and belongs to storage.py, not to any
    isolation mechanism.

Out of scope

  • devpod's ~/.ssh/config lost-update race. writeSSHConfig rewrites the
    whole file under a process-local sync.Mutex only, so two host-side dl
    processes — or dl racing a user's own devpod up — can each clobber the
    other's block, and the user's personal entries are in the blast radius.
    Established from source while resolving the mount question. It is an upstream
    fork defect affecting plain host use, independent of anything this map
    changes, and narrowing the mount only hides it from the container. Fixing it
    means patching skevetter/devpod, which is past this map's destination.

  • The claude-code feature's mount drift
    #108, filed as a plain issue
    rather than tracked here. Its README documents granular per-file mounts with
    CLAUDE.md/agents//commands//hooks/ read-only on an explicit
    prompt-injection argument; devcontainer-feature.json mounts the whole of
    ~/.claude read-write, so the documented protection is absent. Found
    incidentally by the networking ticket. Real, but it is about what the feature
    mounts, not about nesting a Docker daemon — past this map's destination in a
    direction the map has no opinion on.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions