Skip to content

fix(ci): mount the checkout at its host path in container-run.sh and the devcontainer - #11460

Merged
basvandijk merged 6 commits into
masterfrom
bas/container-run-mount-checkout-at-host-path
Sep 7, 2026
Merged

fix(ci): mount the checkout at its host path in container-run.sh and the devcontainer#11460
basvandijk merged 6 commits into
masterfrom
bas/container-run-mount-checkout-at-host-path

Conversation

@basvandijk

@basvandijk basvandijk commented Sep 5, 2026

Copy link
Copy Markdown
Collaborator

Problem

ci/container/container-run.sh mounted 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-checkout startup --output_base rc file via BAZELRC, and linked git worktrees needed the main repository's .git mounted at its host path because their gitdir: pointers are absolute host paths. The VS Code devcontainer had the same /ic layout and shared md5(/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.sh and 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 #11441 BAZELRC block and its host-side write under ~/.cache/container-run/ are gone; linked worktrees keep working with the existing GIT_COMMON_DIR mount; host paths are valid inside the container (e.g. "$(git rev-parse --show-toplevel)"/ci/container/build-ic.sh works); the -c/--cache-dir directory is created if missing (the removed block used to do that as a side effect). gc.worktreePruneExpire=never is now set in every container: a main-checkout container sees the other worktrees as prunable and git gc --auto could otherwise prune host worktrees idle for more than three months.
  • devcontainer: workspaceMount/workspaceFolder use ${localWorkspaceFolder}. A devcontainer and a container-run.sh container on the same checkout must not share an output base, so initializeCommand (run on the host by devcontainer up itself, independent of whether VS Code runs the in-container lifecycle hooks) writes ~/.cache/bazel/devcontainer/${devcontainerId}.bazelrc pinning devcontainer-${devcontainerId}, and containerEnv.BAZELRC points bazel at it. Install base, repository cache and ~/.cache/cargo stay shared; bazel/bazel_clean.sh cleans all of it. The devcontainer now also sets gc.worktreePruneExpire=never (same hazard as above), and search.exclude matches bazel-*/ since the convenience symlink is bazel-<checkout directory name>.
  • Docs no longer claim the checkout lives at /ic; the stale 2023 podman run example in ci/container/README.md is replaced by a description of what the container sees.

No Dockerfile/TAG change (Dockerfile:73 still has an inert /ic/bin PATH entry; 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 --help via relative and absolute path, bazel info output_base equals md5(host path) per checkout, the main checkout's bazel server pid is unchanged across a concurrent bazel run from a worktree container, and a devcontainer up of a throwaway clone via the Dev Containers CLI yields BAZELRC set and bazel info output_base = devcontainer-<devcontainerId>. CI: the existing Test Container Run legs and Repro check (user ubuntu, clone nested under ~/.cache, nested podman) cover the two CI shapes; an integration test script follows in a stacked PR.

basvandijk and others added 4 commits September 5, 2026 14:38
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>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 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>
@basvandijk
basvandijk added this pull request to the merge queue Sep 7, 2026
Merged via the queue into master with commit 0f71db7 Sep 7, 2026
78 of 79 checks passed
@basvandijk
basvandijk deleted the bas/container-run-mount-checkout-at-host-path branch September 7, 2026 14:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants