From 111a633c8089faaff573ef96a4f699727801b91b Mon Sep 17 00:00:00 2001 From: Cedric Vidal Date: Sun, 13 Sep 2026 01:48:54 -0700 Subject: [PATCH 1/2] feat(workers): install the GitHub CLI in coding agent images Coding agents are routinely asked to work with issues, branches and pull requests, but `gh` was not available in any worker image, so a task that needed it could only fail or fall back to hand-rolled API calls. Install it alongside the other system tools each image already ships (Python, Go, .NET, Java, Maven, Gradle, PowerShell), pinned to 2.100.0 for reproducible builds: - coder-acp-copilot and coder-acp-claude-code: fold the release tarball into the existing toolchain layer, reusing its `ARCH` so amd64 and arm64 both resolve (gh's asset names match `dpkg --print-architecture` exactly). The layer ends in `gh --version` so a bad URL fails the build instead of silently producing an image without it. - coder-acp-copilot-windows: install through Chocolatey next to git. No version check here, because the Chocolatey shim directory only joins PATH via the later ENV, so `gh` is not yet invokable at that layer. `GH_VERSION` is exported at runtime so the agent version registration can report it next to COPILOT_CLI_VERSION. Verified by building the coder-acp-copilot `base` stage and running the resulting image: gh version 2.100.0 (2026-09-03) GH_VERSION=2.100.0 /usr/local/bin/gh Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 507f8ebd-cc32-489c-afca-8941c5f8dba1 --- apps/workers/coder-acp-claude-code/Dockerfile | 14 +++++++++++++- .../coder-acp-copilot-windows/Dockerfile.base | 6 ++++++ apps/workers/coder-acp-copilot/Dockerfile | 14 +++++++++++++- 3 files changed, 32 insertions(+), 2 deletions(-) diff --git a/apps/workers/coder-acp-claude-code/Dockerfile b/apps/workers/coder-acp-claude-code/Dockerfile index e888312ef..6c0a15bb3 100644 --- a/apps/workers/coder-acp-claude-code/Dockerfile +++ b/apps/workers/coder-acp-claude-code/Dockerfile @@ -8,7 +8,10 @@ ENV npm_config_registry=${NPM_CONFIG_REGISTRY} \ # which some private registry proxies don't serve; fall back to a pinned global # install (honors npm_config_registry) so restricted networks can still build. RUN corepack enable && (corepack prepare pnpm@10.29.1 --activate || npm install -g pnpm@10.29.1 --force) -# Install system tools for coding agents: Python, uv, git, PowerShell, Go, .NET, Rust, Java, Maven, Gradle +# Pinned like every other system tool below so image builds stay reproducible. +ARG GH_VERSION=2.100.0 + +# Install system tools for coding agents: Python, uv, git, GitHub CLI, PowerShell, Go, .NET, Rust, Java, Maven, Gradle RUN apt-get update && apt-get install -y \ python3 \ python3-pip \ @@ -64,9 +67,18 @@ RUN apt-get update && apt-get install -y \ && rm /tmp/gradle.zip \ && chmod +x /opt/gradle/gradle-8.12/bin/gradle \ && ln -s /opt/gradle/gradle-8.12/bin/gradle /usr/local/bin/gradle \ + && curl -fsSL -o /tmp/gh.tar.gz \ + "https://github.com/cli/cli/releases/download/v${GH_VERSION}/gh_${GH_VERSION}_linux_${ARCH}.tar.gz" \ + && tar -xzf /tmp/gh.tar.gz -C /tmp \ + && mv "/tmp/gh_${GH_VERSION}_linux_${ARCH}/bin/gh" /usr/local/bin/gh \ + && chmod +x /usr/local/bin/gh \ + && rm -rf /tmp/gh.tar.gz "/tmp/gh_${GH_VERSION}_linux_${ARCH}" \ + && gh --version \ && rm -rf /var/lib/apt/lists/* ENV PATH="/root/.cargo/bin:/root/.local/bin:$PATH" +# Surfaced at runtime so the worker can report the toolchain it actually shipped. +ENV GH_VERSION=${GH_VERSION} # Disable Claude Code "policy skills" (auto-loaded, Anthropic-managed Agent Skills). # As of claude-agent-acp 0.52.0 / claude-agent-sdk 0.3.191 the bundled agent auto-invokes diff --git a/apps/workers/coder-acp-copilot-windows/Dockerfile.base b/apps/workers/coder-acp-copilot-windows/Dockerfile.base index 441af47d6..881f55549 100644 --- a/apps/workers/coder-acp-copilot-windows/Dockerfile.base +++ b/apps/workers/coder-acp-copilot-windows/Dockerfile.base @@ -20,6 +20,12 @@ RUN Invoke-WebRequest -Uri "https://nodejs.org/dist/v${env:NODE_VERSION}/node-v$ # Install git via Chocolatey RUN choco install -y git +# Install GitHub CLI via Chocolatey (pinned to match the Linux worker images) +# No version check here: the chocolatey shim directory only joins PATH via the +# ENV below, so `gh` is not yet invokable at this layer (same as git above). +ARG GH_VERSION=2.100.0 +RUN choco install -y gh --version=$env:GH_VERSION + # Install pnpm as a standalone executable RUN New-Item -ItemType Directory -Force -Path C:\tools | Out-Null; \ Invoke-WebRequest -Uri 'https://github.com/pnpm/pnpm/releases/download/v10.29.1/pnpm-win-x64.exe' \ diff --git a/apps/workers/coder-acp-copilot/Dockerfile b/apps/workers/coder-acp-copilot/Dockerfile index a34e9ccd7..1d184092d 100644 --- a/apps/workers/coder-acp-copilot/Dockerfile +++ b/apps/workers/coder-acp-copilot/Dockerfile @@ -10,7 +10,10 @@ ENV npm_config_registry=${NPM_CONFIG_REGISTRY} \ # which some private registry proxies don't serve; fall back to a pinned global # install (honors npm_config_registry) so restricted networks can still build. RUN corepack enable && (corepack prepare pnpm@10.29.1 --activate || npm install -g pnpm@10.29.1 --force) -# Install system tools for coding agents: Python, uv, git, PowerShell, Go, .NET, Rust, Java, Maven, Gradle +# Pinned like every other system tool below so image builds stay reproducible. +ARG GH_VERSION=2.100.0 + +# Install system tools for coding agents: Python, uv, git, GitHub CLI, PowerShell, Go, .NET, Rust, Java, Maven, Gradle RUN apt-get update && apt-get install -y \ python3 \ python3-pip \ @@ -68,9 +71,18 @@ RUN apt-get update && apt-get install -y \ && rm /tmp/gradle.zip \ && chmod +x /opt/gradle/gradle-8.12/bin/gradle \ && ln -s /opt/gradle/gradle-8.12/bin/gradle /usr/local/bin/gradle \ + && curl -fsSL -o /tmp/gh.tar.gz \ + "https://github.com/cli/cli/releases/download/v${GH_VERSION}/gh_${GH_VERSION}_linux_${ARCH}.tar.gz" \ + && tar -xzf /tmp/gh.tar.gz -C /tmp \ + && mv "/tmp/gh_${GH_VERSION}_linux_${ARCH}/bin/gh" /usr/local/bin/gh \ + && chmod +x /usr/local/bin/gh \ + && rm -rf /tmp/gh.tar.gz "/tmp/gh_${GH_VERSION}_linux_${ARCH}" \ + && gh --version \ && rm -rf /var/lib/apt/lists/* ENV PATH="/root/.cargo/bin:/root/.local/bin:$PATH" +# Surfaced at runtime so the worker can report the toolchain it actually shipped. +ENV GH_VERSION=${GH_VERSION} # --- Builder stage: compile TypeScript --- FROM base AS builder From 8c1dd71e9c65fb52f54ce222ef616f8a578cc135 Mon Sep 17 00:00:00 2001 From: Cedric Vidal Date: Sun, 13 Sep 2026 02:20:22 -0700 Subject: [PATCH 2/2] fix(compose): allow agents to use the Docker socket on SELinux hosts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The coder services mount the host Docker socket and set DOCKER_HOST so the agent can run containers during a task. `group_add: ${DOCKER_GID:-0}` gives the right GID, but that is only half of what the socket needs. Where the daemon host enforces SELinux — a podman machine always does, and RHEL/Fedora Docker Engine can — the worker runs as `container_t` while the socket is labelled `var_run_t`, and policy denies the connect. The agent then cannot run containers at all, and the failure is easy to misread: it surfaces as `permission denied ... /var/run/docker.sock`, which looks like a GID problem that DOCKER_GID has already solved. Even `stat` on the socket is denied, which is the tell that it is the label and not the mode. Measured on a podman machine with SELinux enforcing, mounting the socket into the copilot worker image: security_opt group_add 0 result (none) no denied label=disable no denied label=type:container_runtime_t no denied (none) yes denied label=disable yes OK label=type:container_runtime_t yes OK So both are required. Add `security_opt: label=disable` next to the existing group_add. Docker ignores label options on hosts without SELinux, so this is a no-op under Docker Desktop and leaves those setups unchanged. `label=type:container_runtime_t` works equally well and keeps the container confined, which is preferable in principle, but it depends on container-selinux providing that type and fails closed when it does not. For a local development stack the portable option is the better default; the alternative is noted in a comment. Verified by recreating coder-acp-copilot from the compose files alone: the container reports SecurityOpt ["label=disable"], GroupAdd ["0"], and `docker ps` from inside the agent's user now succeeds. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 507f8ebd-cc32-489c-afca-8941c5f8dba1 --- docker-compose.yml | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/docker-compose.yml b/docker-compose.yml index fac8bcd69..b63fbd869 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -607,6 +607,15 @@ services: profiles: [claude-code] group_add: - "${DOCKER_GID:-0}" # Docker socket GID (0 for macOS/Docker Desktop, set DOCKER_GID for Linux) + security_opt: + # The right GID is necessary but not sufficient when the daemon host + # enforces SELinux (a podman machine always does; RHEL/Fedora Docker Engine + # can): container_t may not open a var_run_t socket, so the mounted socket + # stays unusable and the agent cannot run containers at all. Ignored on + # hosts without SELinux, such as Docker Desktop. + # label=type:container_runtime_t also works and keeps confinement, but it + # requires container-selinux to provide that type. + - label=disable environment: <<: *worker-env WORKER_NAME: coder-acp-claude-code @@ -713,6 +722,15 @@ services: profiles: [copilot] group_add: - "${DOCKER_GID:-0}" # Docker socket GID (0 for macOS/Docker Desktop, set DOCKER_GID for Linux) + security_opt: + # The right GID is necessary but not sufficient when the daemon host + # enforces SELinux (a podman machine always does; RHEL/Fedora Docker Engine + # can): container_t may not open a var_run_t socket, so the mounted socket + # stays unusable and the agent cannot run containers at all. Ignored on + # hosts without SELinux, such as Docker Desktop. + # label=type:container_runtime_t also works and keeps confinement, but it + # requires container-selinux to provide that type. + - label=disable environment: <<: *worker-env WORKER_NAME: coder-acp-copilot