Files
agent-runtime-framework/harnesses/contexts/repo-readonly/v1/init.sh
Paul O'Reilly d850a4da68 fix(repo-readonly): clone when /workspace/project is empty, not only when absent (bug eede60c3)
harness_init pre-creates an empty /workspace/project, so the -d guard always skipped the clone.

Claude-Session: https://claude.ai/code/session_01N218bERyNeLhbLmDRDvbWF
2026-10-02 17:21:35 +13:00

81 lines
3.9 KiB
Bash
Executable File

#!/bin/bash
# repo-readonly init — read-only project-repo clone for harness-path
# kind: script nodes (spec/typed-workflows.md WF-COMP-34, bug 3bee78fa).
#
# Threat model: the script node is invoked by entrypoint/runners/shell.py
# with cwd=context["workspace"]; the existing entrypoint paths set
# context["workspace"] to /workspace/project from AGENT_WORKING_DIR
# (the env var this init.sh exports via its harness.yaml env field).
# The init.sh MUST clone AGENT_REPO_URL (the upstream project repo URL
# emitted by the WF-COMP-34 compiler emission as payload.agent_repo.url
# and propagated to the container env by the dispatcher) into
# /workspace/project at its default branch — read-only, no fork, no push,
# no AR-39 select-base, no scratchpad, no .gitignore munging.
#
# Bug discovered live 2026-10-02 (bug 3bee78fa, run
# 9a368e71-d6ab-4185-a98c-9b31d41bb4b5): without this layer, every
# harness-path kind: script node ran with /workspace/project missing,
# so any preflight that read repo files (e.g. preflight-test-collection
# invoking pytest --collect-only tests/test_k8s_gc.py) saw an empty cwd
# and either reported zero files or failed with the upstream tool's own
# misconfig error. The WF-COMP-34 compiler emission (this repo's
# controlplane/typed_workflows/compiler.py, bug 3bee78fa) writes
# payload.agent_repo onto every harness-path script node's manifest, so
# the URL reaches the env when the template (or the node) declares
# agent_repo. This init.sh is the other half — the layer that actually
# clones it.
set -euo pipefail
echo "=== repo-readonly/v1 init.sh ==="
# WF-COMP-34 unset-case contract: if AGENT_REPO_URL is unset, this is a
# no-op. The script node keeps its pre-fix silent empty workspace
# runtime exactly — a template that omits agent_repo does not get a
# surprise clone. The entrypoint V2 guard (entrypoint/entrypoint.py:901-906)
# only fires when AGENT_REPO_URL IS set; the unset case is a no-op on
# both sides (no clone here, no V2 trip there), so the legacy
# script_on_harness=False path's observable behaviour is preserved
# verbatim.
if [ -z "${AGENT_REPO_URL:-}" ]; then
echo "AGENT_REPO_URL unset; no clone (WF-COMP-34 unset-case contract)."
exit 0
fi
# If the clone target already has content (e.g. a previous layer cloned
# into it), skip — never clobber. The check is on NON-EMPTY, not on
# existence: entrypoint/harness_init.py always pre-creates an empty
# /workspace/project, so a bare -d test skipped the clone every time
# (bug eede60c3). git clone into an existing empty directory is valid.
if [ -d /workspace/project ] && [ -n "$(ls -A /workspace/project 2>/dev/null)" ]; then
echo "/workspace/project already populated; skipping clone."
exit 0
fi
mkdir -p /workspace
echo "Cloning (read-only): $AGENT_REPO_URL -> /workspace/project"
# Read-only clone: no checkout of a task branch, no push target, no
# remote operations beyond the initial clone. --depth 1 is enough —
# a script node reads files, not history. If history is needed later,
# the spec can be amended; today every preflight is a pytest --collect-only
# or a prettier --check or similar that does not need commit ancestry.
if git clone --depth 1 "$AGENT_REPO_URL" /workspace/project; then
echo "Cloned $AGENT_REPO_URL to /workspace/project"
else
# Loud failure: a harness-path script node that declares agent_repo
# and finds the clone fails SHOULD fail the task at init time, not
# silently leave /workspace/project missing for the V2 guard to
# catch 26 seconds later (that failure mode is the symptom this
# bug was filed for).
echo "ERROR: Failed to clone $AGENT_REPO_URL" >&2
exit 1
fi
# Strip any symlinks (security: prevent /proc/1/environ exfiltration;
# mirrors agent-repo/v1's defence).
find /workspace/project -type l -exec rm {} \; 2>/dev/null || true
echo "AGENT_WORKING_DIR=${AGENT_WORKING_DIR:-/workspace/project}"
echo "=== repo-readonly/v1 init.sh complete ==="