Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 13 additions & 2 deletions docker/Dockerfile.runtime
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
#
# Contract (must match convert_skillsbench_task.py's inject block + the host):
# /opt/coder-eval/venv/bin/coder-eval the CLI (its own Python)
# /opt/coder-eval/node/bin/{node,claude} Node + the Claude Code CLI
# /opt/coder-eval/node/bin/{node,claude,pi} Node + the Claude Code and Pi CLIs
# /usr/local/bin/coder_eval_entrypoint.sh entrypoint (host pins --entrypoint here)
# LABEL org.coder-eval.version=<ver> for `docker inspect` / parity
#
Expand Down Expand Up @@ -83,6 +83,17 @@ RUN mkdir -p /opt/coder-eval/node \
&& PATH="/opt/coder-eval/node/bin:${PATH}" npm install -g --prefix /opt/coder-eval/node \
@anthropic-ai/claude-code@${CLAUDE_CODE_VERSION}

# --- 4. The Pi CLI, under the kit dir. --------------------------------------
# Same package and pin as docker/Dockerfile, so `--driver docker --type pi` runs
# in an injected task image as it does in a rebased one; a parity test
# (tests/test_image_from_dockerfiles.py) keeps the two PI_VERSION pins equal.
# `pi --version` fails the build here if the package's bin did not land on the
# kit's PATH, instead of every pi task failing later with "pi CLI not found".
ARG PI_VERSION=0.87.1
RUN PATH="/opt/coder-eval/node/bin:${PATH}" npm install -g --prefix /opt/coder-eval/node \
@earendil-works/pi-coding-agent@${PI_VERSION} \
&& PATH="/opt/coder-eval/node/bin:${PATH}" pi --version

# Sanity: the CLI runs from the kit (no reliance on a system Python). Run it in
# the builder, where the kit lives; the final stage is a bare carrier.
RUN /opt/coder-eval/venv/bin/coder-eval _run-task-internal --help > /dev/null
Expand All @@ -99,7 +110,7 @@ COPY --from=builder /opt/coder-eval /opt/coder-eval
# Entrypoint: puts the kit's bins on PATH, then runs the orchestrator. Same
# filename + location as the framework image, so the host's fixed
# `--entrypoint /usr/local/bin/coder_eval_entrypoint.sh` works for injected
# images too — coder-eval/claude/node all resolve from the kit regardless of what
# images too — coder-eval/claude/pi/node all resolve from the kit regardless of what
# the task's base image provides.
COPY docker/coder_eval_runtime_entrypoint.sh /usr/local/bin/coder_eval_entrypoint.sh
RUN chmod +x /usr/local/bin/coder_eval_entrypoint.sh
Expand Down
2 changes: 1 addition & 1 deletion docs/DOCKER_ISOLATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ LABEL org.coder-eval.version="<ver>"
```

`make coder-eval-runtime` builds the kit (`docker/Dockerfile.runtime`): a
standalone CPython + Node + the `coder-eval` CLI + Claude Code, all under
standalone CPython + Node + the `coder-eval` CLI + Claude Code + Pi, all under
`/opt/coder-eval`, plus the entrypoint at the **same** `/usr/local/bin/...` path
the host pins. The kit is **glibc-only** — it runs on debian/ubuntu/fedora/rhel/…
but not musl/Alpine.
Expand Down
5 changes: 3 additions & 2 deletions docs/agents/PI.md
Original file line number Diff line number Diff line change
Expand Up @@ -216,8 +216,9 @@ success — the error names the unrecognized event types it saw. Intentional cut
## Running in Docker

Pi **is supported under `--driver docker`.** The pinned Pi CLI is baked into
`docker/Dockerfile` (`ARG PI_VERSION`, alongside the claude-code CLI), and
`OPENROUTER_API_KEY` is forwarded into the container by the docker driver's
`docker/Dockerfile` (`ARG PI_VERSION`, alongside the claude-code CLI) and, at the
same pin, into the runtime kit (`docker/Dockerfile.runtime`), so a task that
brings its own base image runs Pi too. `OPENROUTER_API_KEY` is forwarded into the container by the docker driver's
default `env_passthrough` allowlist — so `--driver docker --type pi` runs
end-to-end with no per-task `env_passthrough_extra`:

Expand Down
27 changes: 27 additions & 0 deletions tests/test_image_from_dockerfiles.py
Original file line number Diff line number Diff line change
Expand Up @@ -485,6 +485,33 @@ def _pin(df: Path) -> str | None:
)


def test_pi_version_pin_matches_framework() -> None:
"""The runtime kit must install Pi at the framework image's PI_VERSION —
else `--type pi` would find no CLI in an injected task image, or run a
different Pi than a rebased one."""
root = Path(__file__).resolve().parents[1]
runtime_df = _runtime_dockerfile()
if runtime_df is None:
pytest.skip("docker/Dockerfile.runtime not present")

def _pin(df: Path) -> str | None:
for ln in df.read_text(encoding="utf-8").splitlines():
m = re.match(r"\s*ARG PI_VERSION=(\S+)", ln)
if m:
return m.group(1)
return None

fw, kit = _pin(root / "docker" / "Dockerfile"), _pin(runtime_df)
assert fw and kit, f"PI_VERSION ARG missing (framework={fw!r}, kit={kit!r})"
assert fw == kit, (
f"PI_VERSION drift: docker/Dockerfile pins {fw!r} but docker/Dockerfile.runtime pins {kit!r} — "
"inject and rebase tasks would run different Pi versions."
)
assert "@earendil-works/pi-coding-agent@${PI_VERSION}" in runtime_df.read_text(encoding="utf-8"), (
"docker/Dockerfile.runtime must install @earendil-works/pi-coding-agent at ${PI_VERSION}."
)


def test_uv_pinned_in_agent_image() -> None:
"""docker/Dockerfile must install a PINNED uv through the versioned
installer URL. Agents run `uv` themselves, so its install speed is part of
Expand Down
Loading