fix(ci): mount the checkout at its host path in container-run.sh and the devcontainer - #11460
Merged
Merged
Conversation
Every checkout used to be mounted at /ic, so bazel's default output base (md5 of the workspace path) was the same for all of them and #11441 had to inject a per-checkout `startup --output_base` via a generated rc file. Mount the checkout at its canonical host path instead and use that as the working directory. Bazel then derives a unique output base per checkout on its own (install base, repository cache and repo contents cache stay shared), the absolute gitdir pointers of linked worktrees resolve together with the existing GIT_COMMON_DIR mount, and host paths stay valid inside the container. The rc-file injection, its BAZELRC variable and the host-side write under ~/.cache/container-run are removed; the cache directory given with -c/--cache-dir is now created explicitly (the removed write used to do that as a side effect). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ontainer gc.worktreePruneExpire=never was only injected when the container was started from a linked worktree. A container started from the main checkout sees the same .git/worktrees/* entries pointing at host paths that are not mounted, but received no such setting, so `git gc --auto` there could prune host worktrees idle for longer than the default three months. Inject the setting unconditionally. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Mount the checkout at the same absolute path it has on the host and use it as the workspace folder, like ci/container/container-run.sh now does, so host paths stay valid inside the devcontainer and bazel derives a per-checkout output base from the workspace path. A devcontainer and a container-run.sh container on the same checkout must not share an output base (bazel cannot see a server in another PID namespace and the two would kill each other's server), so postCreateCommand pins a devcontainer-specific output base in the container-local ~/.bazelrc. The install base and the repository cache stay shared. The lifecycle commands derive the path from $PWD and use the array form so that a checkout path with spaces cannot break them. Like container-run.sh, the devcontainer now sets gc.worktreePruneExpire=never: the checkout's .git/worktrees/* entries point at host paths of other worktrees that are not mounted, so `git gc --auto` could prune them. Bazel's convenience symlink is bazel-<workspace directory name>, which is only bazel-ic when the checkout directory happens to be called ic, so the VS Code search.exclude setting now matches bazel-*/ instead of listing bazel-bin/, bazel-ic/ and bazel-out/. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Replace the stale 2023 `podman run` example in ci/container/README.md with a description of what the container sees, describe the per-checkout default output base instead of the removed BAZELRC mechanism (including which old output bases can be deleted), note that ~/.container-run.conf arguments are passed to docker too, and update the docs and skills that asserted the checkout lives at /ic. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Contributor
There was a problem hiding this comment.
🔵 Needs a closer look
Cross-runtime mount ordering, path handling, and Bazel cache isolation require final human validation.
Pull request overview
Updates container tooling to preserve host checkout paths, isolating Bazel output bases across checkouts and devcontainers.
Changes:
- Mounts checkouts at canonical host paths and removes generated Bazel RC files.
- Assigns devcontainers a dedicated Bazel output base.
- Updates Git safeguards and
/ic-specific documentation.
File summaries
| File | Description |
|---|---|
ci/container/container-run.sh |
Changes checkout mounting and Git GC configuration. |
.devcontainer/devcontainer.json |
Updates workspace paths and Bazel isolation. |
ci/container/README.md |
Documents the new container layout. |
rs/tests/README.md |
Removes the /ic prompt assumption. |
rs/tests/README_NEW.md |
Removes the /ic prompt assumption. |
ic-os/README.adoc |
Makes output paths repository-relative. |
ic-os/docs/Upgrades.adoc |
Makes upgrade-image paths repository-relative. |
ic-os/dev-tools/bare_metal_deployment/README.md |
Updates mount-path guidance. |
.claude/skills/run-in-dev-container/SKILL.md |
Documents host-path mounting. |
.claude/skills/fix-build-determinism/SKILL.md |
Replaces /ic artifact paths. |
.claude/CLAUDE.md |
Updates repository-root guidance. |
Review details
- Files reviewed: 11/11 changed files
- Comments generated: 0
- Review effort level: Balanced
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
The pinned output base was written to the container-local ~/.bazelrc by postCreateCommand. VS Code does not run the in-container lifecycle commands in every setup (observed here with Dev Containers over Remote-SSH: `up` is invoked with --skip-post-create and the follow-up run-user-commands never happens), in which case the devcontainer silently fell back to the default output base and could collide with a container-run.sh container on the same checkout. initializeCommand is executed on the host by `devcontainer up` itself, so use it to write the rc file to ~/.cache/bazel/devcontainer/<devcontainerId>.bazelrc (under the mounted ~/.cache, and under ~/.cache/bazel so bazel/bazel_clean.sh removes it) and point bazel at it with containerEnv.BAZELRC. The key is the devcontainer's ${devcontainerId}, which is unique per local folder, stable across rebuilds and substituted by the CLI, so no host tooling (md5sum) or path quoting is involved. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
nmattia
approved these changes
Sep 7, 2026
blind-oracle
approved these changes
Sep 7, 2026
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.
Problem
ci/container/container-run.shmounted every checkout at/ic. Bazel derives its default output base from the workspace path, so all checkouts shared one output base and containers started from two checkouts killed each other's bazel server (Server terminated abruptly (error code: 14, ...), each container has its own PID namespace). #11441 worked around that by injecting a generated per-checkoutstartup --output_baserc file viaBAZELRC, and linked git worktrees needed the main repository's.gitmounted at its host path because theirgitdir:pointers are absolute host paths. The VS Code devcontainer had the same/iclayout and sharedmd5(/ic)between all checkouts.Change
The checkout is now bind-mounted at the same absolute (canonical) path it has on the host, which is also the working directory, both in
container-run.shand in the devcontainer.container-run.sh: every checkout gets its own default output base (~/.cache/bazel/_bazel_<user>/<md5 of the host path>) with no rc-file trick, so the fix(ci): give each checkout its own bazel output base in container-run.sh #11441BAZELRCblock and its host-side write under~/.cache/container-run/are gone; linked worktrees keep working with the existingGIT_COMMON_DIRmount; host paths are valid inside the container (e.g."$(git rev-parse --show-toplevel)"/ci/container/build-ic.shworks); the-c/--cache-dirdirectory is created if missing (the removed block used to do that as a side effect).gc.worktreePruneExpire=neveris now set in every container: a main-checkout container sees the other worktrees as prunable andgit gc --autocould otherwise prune host worktrees idle for more than three months.workspaceMount/workspaceFolderuse${localWorkspaceFolder}. A devcontainer and acontainer-run.shcontainer on the same checkout must not share an output base, soinitializeCommand(run on the host bydevcontainer upitself, independent of whether VS Code runs the in-container lifecycle hooks) writes~/.cache/bazel/devcontainer/${devcontainerId}.bazelrcpinningdevcontainer-${devcontainerId}, andcontainerEnv.BAZELRCpoints bazel at it. Install base, repository cache and~/.cache/cargostay shared;bazel/bazel_clean.shcleans all of it. The devcontainer now also setsgc.worktreePruneExpire=never(same hazard as above), andsearch.excludematchesbazel-*/since the convenience symlink isbazel-<checkout directory name>./ic; the stale 2023podman runexample inci/container/README.mdis replaced by a description of what the container sees.No Dockerfile/TAG change (
Dockerfile:73still has an inert/ic/binPATHentry; left for the next image rebuild).Verified on a devenv: podman from the main checkout and from a linked worktree, docker from the main checkout,
build-ic.sh --helpvia relative and absolute path,bazel info output_baseequalsmd5(host path)per checkout, the main checkout's bazel server pid is unchanged across a concurrent bazel run from a worktree container, and adevcontainer upof a throwaway clone via the Dev Containers CLI yieldsBAZELRCset andbazel info output_base=devcontainer-<devcontainerId>. CI: the existingTest Container Runlegs andRepro check(userubuntu, clone nested under~/.cache, nested podman) cover the two CI shapes; an integration test script follows in a stacked PR.