#!/bin/bash # Agent repo init script — clones reference repos and working repo # AR-12 through AR-15, AR-32 set -euo pipefail echo "=== agent-repo/v1 init.sh ===" # Ensure workspace directories exist mkdir -p /workspace/reference mkdir -p /workspace/project mkdir -p /workspace/.agent-output # Clone reference branches (AR-12, AR-13) if [ -n "${REFERENCE_BRANCHES:-}" ] && [ "${REFERENCE_BRANCHES:-}" != "[]" ]; then echo "Cloning reference branches..." echo "$REFERENCE_BRANCHES" | python3 -u -c " import json, sys, subprocess, os refs = json.load(sys.stdin) print(f'reference_branches loop: {len(refs)} entries') for ref in refs: name = ref.get('name', '') repo_url = ref.get('repo_url', '') branch = ref.get('branch', 'main') dest = f'/workspace/reference/{name}' print(f'Cloning {repo_url} ({branch}) -> {dest}') # Full clone (no --depth) so AR-14a-seeded agent branches have visible # ancestry when pushed back to the agent-repo. Gitea rejects shallow # pushes with shallow-update-not-allowed. Real incident: 2026-05-08 # probe 7. result = subprocess.run( ['git', 'clone', '--branch', branch, '-c', 'core.symlinks=false', repo_url, dest], capture_output=True, text=True ) print(f'clone returncode={result.returncode}') if result.stdout: print(f'clone stdout (last 1KB): {result.stdout[-1000:]}') if result.stderr: print(f'clone stderr (last 1KB): {result.stderr[-1000:]}') if result.returncode != 0: print(f'ERROR: Failed to clone {repo_url}', file=sys.stderr) sys.exit(1) # Strip any symlinks (security: prevent /proc/1/environ exfiltration) subprocess.run(['find', dest, '-type', 'l', '-exec', 'rm', '{}', ';']) # Make reference read-only chmod_r = subprocess.run(['chmod', '-R', 'a-w', dest], capture_output=True, text=True) if chmod_r.returncode != 0: print(f'WARNING: chmod returned {chmod_r.returncode}: {chmod_r.stderr[:300]}') if not os.path.isdir(os.path.join(dest, '.git')): print(f'ERROR: clone exit 0 but {dest}/.git missing', file=sys.stderr) sys.exit(1) print(f'Cloned {name} successfully') " fi # Clone agent repo working branch (AR-14). # # AR-14a (2026-05-08): seed fresh task branches from /workspace/reference/main/ # rather than the agent-repo fork's main, so a stale fork (e.g. periodic # "Fork cleanup" PRs that reset main) doesn't poison every fresh task with # old project state. Existing AGENT_BRANCH cherry-picks remain unchanged # (continuing prior work). Pre-existing operator workaround in # memory/gotchas-airouter.md item 27. if [ -n "${AGENT_REPO_URL:-}" ] && [ -n "${AGENT_BRANCH:-}" ]; then echo "Cloning agent working repo: $AGENT_REPO_URL (branch: $AGENT_BRANCH)" if git clone --depth 1 --branch "$AGENT_BRANCH" "$AGENT_REPO_URL" /workspace/project 2>/dev/null; then echo "Cloned existing branch $AGENT_BRANCH (continuing prior work)" else echo "Branch $AGENT_BRANCH does not exist — seeding fresh branch from upstream reference" # Full clone (not --depth 1) so we get a working remote for finalize.sh push. if git clone "$AGENT_REPO_URL" /workspace/project; then cd /workspace/project # AR-39: resolve the seed point via entrypoint.agent_repo_base # (upstream > reference > fork). Writes branch_base.json for # finalize.sh to merge into ci_metadata.json (AR-25). Falls back # to the legacy inline AR-14a logic if the CLI is unavailable. mkdir -p /workspace/.agent-output AR39_OUT=$(python3 -m entrypoint.agent_repo_base select-base \ --workdir /workspace/project \ --branch "$AGENT_BRANCH" \ --upstream-url "${AGENT_UPSTREAM_REPO_URL:-}" \ --upstream-branch "${AGENT_UPSTREAM_BRANCH:-main}" \ --reference-dir /workspace/reference/main \ --out /workspace/.agent-output/branch_base.json \ --fork-url "$AGENT_REPO_URL" 2>&1) && AR39_RC=0 || AR39_RC=$? if [ "$AR39_RC" = "0" ]; then AR39_SHA=$(printf '%s\n' "$AR39_OUT" | sed -n 's/^sha=//p' | head -1) AR39_SRC=$(printf '%s\n' "$AR39_OUT" | sed -n 's/^source=//p' | head -1) git checkout -b "$AGENT_BRANCH" "$AR39_SHA" echo "Seeded $AGENT_BRANCH from $AR39_SRC @ $AR39_SHA (AR-39)" else echo "WARNING: AR-39 select-base failed (rc=$AR39_RC): $AR39_OUT — falling back to inline AR-14a seeding" >&2 fi REF_REPO=/workspace/reference/main if [ "$AR39_RC" != "0" ] && [ -d "$REF_REPO/.git" ]; then # Seed working tree from the upstream reference clone — AR-14a. # Reference is read-only (chmod a-w), but git can still read it # as a local-path remote for fetch + reset. # # Use a temporary remote name so we don't collide with 'origin'. git remote add upstream-ref "$REF_REPO" # Full fetch (no --depth) — the agent's branch will be pushed # back to the agent-repo, and gitea rejects shallow pushes # with "shallow update not allowed". Even though the # reference clone itself may be shallow, fetch as much as # the source has so the agent's HEAD has visible ancestry. # Real incident: 2026-05-08 probe 7 (shallow update reject). git fetch upstream-ref 2>&1 | head -3 || { echo "WARNING: failed to fetch from upstream reference; falling back to fork main" >&2 git remote remove upstream-ref 2>/dev/null git checkout -b "$AGENT_BRANCH" } if git rev-parse upstream-ref/HEAD >/dev/null 2>&1; then UPSTREAM_REF="upstream-ref/HEAD" elif git rev-parse upstream-ref/main >/dev/null 2>&1; then UPSTREAM_REF="upstream-ref/main" else UPSTREAM_REF="" fi if [ -n "$UPSTREAM_REF" ]; then git checkout -b "$AGENT_BRANCH" "$UPSTREAM_REF" git remote remove upstream-ref echo "Seeded $AGENT_BRANCH from $REF_REPO ($(git log --oneline -1)) — AR-14a" else git remote remove upstream-ref 2>/dev/null git checkout -b "$AGENT_BRANCH" echo "WARNING: upstream-ref had no resolvable HEAD; using fork main (may be stale)" >&2 fi elif [ "$AR39_RC" != "0" ]; then # No reference clone available — fall back to fork main. git checkout -b "$AGENT_BRANCH" echo "WARNING: $REF_REPO/.git not found; using fork main (may be stale)" >&2 fi else echo "ERROR: Failed to clone agent repo $AGENT_REPO_URL" >&2 exit 1 fi fi fi # Clean up results/ from any previous run (AR-15) if [ -d /workspace/project/results ]; then rm -rf /workspace/project/results fi mkdir -p /workspace/project/results # Ensure scratchpad/ exists at the top level (cross-node artifact passing). # The git branch is this project's shared data channel between manifest # nodes — each node runs in its own ephemeral container/pod with no shared # filesystem, so an agent node that needs to hand data to a downstream # kind: script node (or another agent node) does it by writing here and # committing/pushing, for the next node to clone the same branch and read # back. NEVER merged into main — the no-scratchpad-in-main CI gate # (agent-runtimes .gitea/workflows/build-images.yaml) rejects any commit to # main that still contains this directory. mkdir -p /workspace/project/scratchpad if [ ! -f /workspace/project/scratchpad/README.md ]; then cat > /workspace/project/scratchpad/README.md << 'SCRATCHPAD_EOF' # scratchpad/ Ephemeral, per-task working data — NOT deliverable content. - Use this directory to pass data between nodes in the same workflow manifest (e.g. an agent node writes a verdict/artifact here for a downstream `kind: script` node to read back off the SAME branch — the git branch is the only thing nodes in a manifest share, since each runs in its own ephemeral container). - Anything under `scratchpad/` is scratch, not review/deliverable content. **Do not** reference or copy `scratchpad/` content into the actual deliverable during integration/merge. - A CI gate rejects any commit to `main` that still contains a `scratchpad/` directory — it must never survive merge. SCRATCHPAD_EOF fi # Populate .gitignore (AR-32) GITIGNORE=/workspace/project/.gitignore cat >> "$GITIGNORE" << 'GITIGNORE_EOF' # Agent-repo auto-generated gitignore entries *.key *.pem *.p12 *.pfx .env *.env *.secret GITIGNORE_EOF # Handle retry: clone previous attempt branch as read-only reference (AR-16) if [ -n "${AGENT_PREVIOUS_BRANCH:-}" ] && [ "${AGENT_RETRY_COUNT:-0}" -gt 0 ]; then echo "Cloning previous attempt branch: $AGENT_PREVIOUS_BRANCH" git clone --depth 1 --branch "$AGENT_PREVIOUS_BRANCH" \ -c core.symlinks=false \ "$AGENT_REPO_URL" /workspace/reference/previous-attempt 2>/dev/null || \ echo "Warning: Could not clone previous attempt branch (non-fatal)" if [ -d /workspace/reference/previous-attempt ]; then find /workspace/reference/previous-attempt -type l -exec rm {} \; chmod -R a-w /workspace/reference/previous-attempt fi fi export AGENT_WORKING_DIR="/workspace/project" echo "AGENT_WORKING_DIR=$AGENT_WORKING_DIR" echo "=== agent-repo/v1 init.sh complete ==="