From 496b821242384c28525ab3de2e60d81f1473acd2 Mon Sep 17 00:00:00 2001 From: Ralph Bean Date: Mon, 29 Jun 2026 13:59:57 -0400 Subject: [PATCH 1/2] refactor(harness): migrate code agent to env.runner/env.sandbox (ADR 0055) Replace runner_env with env.runner and forge.github.runner_env with forge.github.env.runner in the code agent harness. Move simple passthrough vars (ISSUE_NUMBER, GITHUB_ISSUE_URL, GH_TOKEN, git identity) and hardcoded config (MAX_RETRIES, TIMEOUT_SECONDS, GOPATH, GOMODCACHE) from the host .env file into the harness YAML under env.sandbox. The .env file retains only the GIT_SSL_CAINFO shell conditional that cannot be expressed declaratively. Update scaffold integration tests to handle both runner_env and env.runner schemas. Assisted-by: Claude Opus 4.6 Signed-off-by: Ralph Bean --- internal/harness/scaffold_integration_test.go | 6 +-- .../scaffold/fullsend-repo/env/code-agent.env | 45 +++---------------- .../scaffold/fullsend-repo/harness/code.yaml | 35 ++++++++++----- 3 files changed, 34 insertions(+), 52 deletions(-) diff --git a/internal/harness/scaffold_integration_test.go b/internal/harness/scaffold_integration_test.go index 1a3bbe0cc..51c9f98dd 100644 --- a/internal/harness/scaffold_integration_test.go +++ b/internal/harness/scaffold_integration_test.go @@ -287,9 +287,9 @@ func TestLoadRaw_GeneratedWrapperFormat(t *testing.T) { } // TestResolveForge_ScaffoldRunnerEnvMerge verifies that forge resolution -// produces the expected merged runner_env for each scaffold template, with -// both top-level (platform-neutral) and forge.github (platform-specific) -// keys present in the final merged state. +// produces the expected merged runner_env / env.runner for each scaffold +// template, with both top-level (platform-neutral) and forge.github +// (platform-specific) keys present in the final merged state. func TestResolveForge_ScaffoldRunnerEnvMerge(t *testing.T) { dir := t.TempDir() harnessDir := extractScaffoldHarnessDir(t, dir) diff --git a/internal/scaffold/fullsend-repo/env/code-agent.env b/internal/scaffold/fullsend-repo/env/code-agent.env index 9274c57ce..16fdf19bf 100644 --- a/internal/scaffold/fullsend-repo/env/code-agent.env +++ b/internal/scaffold/fullsend-repo/env/code-agent.env @@ -1,42 +1,9 @@ -export ISSUE_NUMBER=${ISSUE_NUMBER} -export GITHUB_ISSUE_URL=${GITHUB_ISSUE_URL} - -# GH_TOKEN in the sandbox is a READ-ONLY scoped app installation token -# (contents:read, issues:read, pull_requests:read). Set by -# setup-agent-env.sh from CODE_GH_TOKEN. This token CANNOT push code -# or create PRs even if the agent bypasses disallowedTools. -# The separate write-enabled PUSH_TOKEN (runner_env) never enters the sandbox. -export GH_TOKEN=${GH_TOKEN} - -# Git identity — uses the GitHub App bot user's noreply email so GitHub -# links commits to the bot account (author.type === "Bot"). This makes -# the Probot DCO app auto-exempt agent commits. The GIT_BOT_EMAIL var -# is resolved at runtime by the "Resolve bot identity" workflow step. -export GIT_AUTHOR_NAME="fullsend-code" -export GIT_AUTHOR_EMAIL="${GIT_BOT_EMAIL}" -export GIT_COMMITTER_NAME="fullsend-code" -export GIT_COMMITTER_EMAIL="${GIT_BOT_EMAIL}" - -# Retry budget — the agent re-runs secret scan + tests on failure. -# Pre-commit is capped at 2 runs total (not per retry) and is NOT -# re-run during retries. The post-script runs authoritative pre-commit. -export MAX_RETRIES=1 - -# Hard timeout for the sandbox session in seconds. The agent uses this to -# check remaining time and avoid burning the budget on retries. Must match -# timeout_minutes in harness/code.yaml. Update this if you change the -# harness timeout. -export TIMEOUT_SECONDS=2100 - -# Go toolchain — PATH, GOPATH, and GOMODCACHE are set in the sandbox image -# via Containerfile ENV (images/code/Containerfile line 64). Do NOT set PATH -# here: this file uses expand: true (harness host_files), so ${PATH} would be -# replaced with the GitHub Actions runner's PATH by os.ExpandEnv, clobbering -# /sandbox/workspace/bin and breaking fullsend scan + any other sandbox-local -# binaries. If OpenShell does not preserve Docker ENV, add Go to PATH via a -# non-expanded host_file or bootstrap step instead of referencing ${PATH} here. -export GOPATH="/sandbox/go" -export GOMODCACHE="/sandbox/go/pkg/mod" +# code-agent.env — shell conditional that cannot be expressed in env.sandbox. +# +# Simple passthrough vars and hardcoded config have been moved to +# harness/code.yaml under env.runner and env.sandbox (ADR 0055). +# Only the GIT_SSL_CAINFO conditional remains here because env.sandbox +# does not support shell logic. # SSL certs — git 2.43 (Ubuntu Noble) is linked against libcurl-gnutls # which does NOT read SSL_CERT_FILE. It requires GIT_SSL_CAINFO explicitly. diff --git a/internal/scaffold/fullsend-repo/harness/code.yaml b/internal/scaffold/fullsend-repo/harness/code.yaml index 1c40b820b..8b62083dc 100644 --- a/internal/scaffold/fullsend-repo/harness/code.yaml +++ b/internal/scaffold/fullsend-repo/harness/code.yaml @@ -41,10 +41,16 @@ plugins: # Environment variables available to pre/post scripts on the runner. # These are expanded from the runner environment and NEVER enter the sandbox. -runner_env: - CODE_ALLOWED_TARGET_BRANCHES: "${CODE_ALLOWED_TARGET_BRANCHES}" - FULLSEND_OUTPUT_SCHEMA: ${FULLSEND_DIR}/schemas/code-result.schema.json - FULLSEND_OUTPUT_FILE: code-result.json +env: + runner: + CODE_ALLOWED_TARGET_BRANCHES: "${CODE_ALLOWED_TARGET_BRANCHES}" + FULLSEND_OUTPUT_SCHEMA: ${FULLSEND_DIR}/schemas/code-result.schema.json + FULLSEND_OUTPUT_FILE: code-result.json + sandbox: + MAX_RETRIES: "1" + TIMEOUT_SECONDS: "2100" + GOPATH: "/sandbox/go" + GOMODCACHE: "/sandbox/go/pkg/mod" timeout_minutes: 35 @@ -52,9 +58,18 @@ forge: github: pre_script: scripts/pre-code.sh post_script: scripts/post-code.sh - runner_env: - PUSH_TOKEN: "${PUSH_TOKEN}" - PUSH_TOKEN_SOURCE: "${PUSH_TOKEN_SOURCE}" - REPO_FULL_NAME: "${REPO_FULL_NAME}" - ISSUE_NUMBER: "${ISSUE_NUMBER}" - REPO_DIR: "${GITHUB_WORKSPACE}/target-repo" + env: + runner: + PUSH_TOKEN: "${PUSH_TOKEN}" + PUSH_TOKEN_SOURCE: "${PUSH_TOKEN_SOURCE}" + REPO_FULL_NAME: "${REPO_FULL_NAME}" + ISSUE_NUMBER: "${ISSUE_NUMBER}" + REPO_DIR: "${GITHUB_WORKSPACE}/target-repo" + sandbox: + ISSUE_NUMBER: "${ISSUE_NUMBER}" + GITHUB_ISSUE_URL: "${GITHUB_ISSUE_URL}" + GH_TOKEN: "${GH_TOKEN}" + GIT_AUTHOR_NAME: "fullsend-code" + GIT_AUTHOR_EMAIL: "${GIT_BOT_EMAIL}" + GIT_COMMITTER_NAME: "fullsend-code" + GIT_COMMITTER_EMAIL: "${GIT_BOT_EMAIL}" From f47cb9f3596e0cb267e4b270ad8aba555ac69504 Mon Sep 17 00:00:00 2001 From: Ralph Bean Date: Mon, 29 Jun 2026 14:49:15 -0400 Subject: [PATCH 2/2] fix(test): update scaffold_test.go for env.runner migration The code harness now uses env.runner/env.sandbox instead of runner_env. Update scaffold_test.go assertions to check both legacy RunnerEnv and new Env.Runner fields. Assisted-by: Claude Opus 4.6 Signed-off-by: Ralph Bean --- internal/scaffold/scaffold_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/scaffold/scaffold_test.go b/internal/scaffold/scaffold_test.go index ee0177f63..a765fc997 100644 --- a/internal/scaffold/scaffold_test.go +++ b/internal/scaffold/scaffold_test.go @@ -530,7 +530,7 @@ func TestCodeHarnessContent(t *testing.T) { assert.Contains(t, s, "agents/code.md") assert.Contains(t, s, "pre_script") assert.Contains(t, s, "post_script") - assert.Contains(t, s, "runner_env") + assert.Contains(t, s, "env:") assert.Contains(t, s, "PUSH_TOKEN") }