# GENERATED — do not edit. Source: scripts/gen-harbor-dockerfiles.ts (fragments + member metadata). # Per-repo harbor task Dockerfile for potion-job-producer. Node service/lambda; no test suite. FROM node:18-bookworm # System deps for this member's runtime + services. RUN apt-get update && apt-get install -y --no-install-recommends \ sudo \ jq \ ca-certificates \ && rm -rf /var/lib/apt/lists/* # --- Python >=3.10 for the reduced-toolset agent's str_replace_editor --- # str_replace_editor (shebang `python3`) uses dataclass(kw_only=True) → it requires Python # >=3.10. The era-matched base ships Debian's older system python (ruby:3.2.1/3.1.2 → 3.9, # ruby:2.6.6 → 3.7), so install a modern CPython via uv (a single static binary that downloads # a managed interpreter — no compile, no apt, works even on archived buster) and make it the # default `python3`. Without this the agent's file editor can't load and every trial dies at # agent setup (NonZeroAgentExitCodeError). Independent of the repo's own runtime. RUN curl -fsSL https://astral.sh/uv/install.sh | env UV_INSTALL_DIR=/usr/local/bin sh \ && uv python install 3.10 \ && ln -sf "$(uv python find 3.10)" /usr/local/bin/python3 \ && python3 --version # Install Claude Code globally (the grader in test.sh runs `claude`). Retry the # network install, then FAIL THE BUILD if `claude` isn't on PATH — a missing grader # CLI silently zeros every reward, so a broken image must NEVER be cached. # NOTE: download-to-file, NOT `curl … | bash` — a pipe returns bash's exit (0 on # empty stdin), masking a failed curl so the retry would break after one attempt. # CLAUDE_CODE_MIN is the oldest CLI the grader model accepts. Referencing it in the RUN # puts it in the layer's cache key, so bumping it rebuilds the install everywhere, and the # assert refuses to cache an image whose installer returned something older. ARG CLAUDE_CODE_MIN=2.1.251 RUN for i in 1 2 3; do \ if curl -fsSL https://claude.ai/install.sh -o /tmp/claude-install.sh && bash /tmp/claude-install.sh; then break; fi; \ echo "WARNING: claude install attempt $i failed; retrying in 5s" >&2; sleep 5; \ done; \ rm -f /tmp/claude-install.sh; \ for p in /root/.claude-code/claude /root/.local/bin/claude "$(find /root -name claude -type f 2>/dev/null | head -1)"; do \ [ -n "$p" ] && [ -x "$p" ] && ln -sf "$p" /usr/local/bin/claude && break; \ done; \ command -v claude >/dev/null 2>&1 || { echo "FATAL: claude CLI not installed (see the install output above) — the grader needs it" >&2; exit 1; }; \ _v="$(claude --version 2>/dev/null | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -1)"; \ [ "$(printf '%s\n%s\n' "$CLAUDE_CODE_MIN" "$_v" | sort -V | head -1)" = "$CLAUDE_CODE_MIN" ] \ || { echo "FATAL: claude $_v is older than $CLAUDE_CODE_MIN, the minimum the grader needs" >&2; exit 1; }; \ echo "claude $_v installed at $(command -v claude)" # Install the Codex CLI at BUILD time, like claude: one fetch per image rather than one per # trial. Never fatal — agent-setup still has the network (the DNS jail lands after it), so a # codex-less image costs a slower first trial, not a broken build on a worker's machine. RUN for i in 1 2 3; do \ if curl -fsSL https://chatgpt.com/codex/install.sh -o /tmp/codex-install.sh \ && CODEX_INSTALL_DIR=/usr/local/bin CODEX_NON_INTERACTIVE=true sh /tmp/codex-install.sh; then break; fi; \ echo "WARNING: codex install attempt $i failed; retrying in 5s" >&2; sleep 5; \ done; \ rm -f /tmp/codex-install.sh; \ if ! command -v codex >/dev/null 2>&1 && [ -x "$HOME/.local/bin/codex" ]; then \ ln -sf "$HOME/.local/bin/codex" /usr/local/bin/codex; \ fi; \ if ! command -v codex >/dev/null 2>&1 && command -v npm >/dev/null 2>&1; then \ npm install -g @openai/codex@latest || true; \ fi; \ command -v codex >/dev/null 2>&1 \ && echo "codex installed at $(command -v codex)" \ || echo "WARNING: codex CLI not installed (see the install output above)" >&2 # Restrict DNS to the model endpoint when DNSJAIL_ALLOW is set (the agent supplies it). # Source: scripts/lib/dns-jail-container.sh, staged here by build-workspace.sh. COPY dns-jail/ /opt/raccoon-dns-jail/ RUN if [ -f /opt/raccoon-dns-jail/dns-jail-container.sh ]; then \ install -m 0755 /opt/raccoon-dns-jail/dns-jail-container.sh /usr/local/bin/raccoon-dns-jail \ && sh -n /usr/local/bin/raccoon-dns-jail; \ else echo "NOTE: no DNS jail script staged; trials on this image run unjailed" >&2; fi # Resolver for the trial DNS allowlist (scripts/dnsjail.py); if this # does not land, trials just run unjailed. RUN (command -v apk >/dev/null 2>&1 && apk add --no-cache dnsmasq bind-tools) \ || (apt-get update && apt-get install -y --no-install-recommends dnsmasq-base dnsutils \ && rm -rf /var/lib/apt/lists/*) \ || true USER root WORKDIR /workspace COPY workspace/ . # Block CC's network tools — agent should execute code locally, not fetch RUN mkdir -p .claude && \ echo '{"permissions":{"deny":["WebFetch","WebSearch"]}}' > .claude/settings.json RUN git init && \ git config user.email "dev@agent" && \ git config user.name "Dev" && \ git add -A && \ git commit -m "initial" --quiet # Install JS deps from the committed yarn.lock; fall back to a plain install if it drifted. RUN yarn install --frozen-lockfile --network-timeout 600000 \ || yarn install --network-timeout 600000 # Fail loudly if any load-bearing tool is missing. RUN for t in node yarn claude; do \ command -v "$t" >/dev/null 2>&1 || { echo "FATAL: required tool '$t' missing from image" >&2; exit 1; }; \ done; \ echo "toolchain OK" CMD ["sleep", "infinity"]