Repository navigation
fix(e2e): close gaps a review found in the paired harness - #1853
Conversation
Bub keeps every session's messages as JSONL in its home, which Harbor
leaves in place between the steps of a trial, so a recall session in
either arm could read what the user said during capture. The agent now
removes Bub's tapes before each session, as OpenCode and Pi already
clear their own session output.
In the JSON mode Harbor uses, Pi exits 0 after a failed model request,
so the arm was graded as an attempt that did not answer. The agent now
reads Pi's last message and raises, which makes the arm an error that
is left out of success rates and paired differences.
Harbor writes each agent's environment to its job files and keeps the
first four and last three characters of a sensitive literal, which is
most of a short token. The harness now holds the Server token it
derives for the ON arm in its own environment and gives Harbor a
reference, so the job files record `${NAME}` and no part of the token.
Evidence redaction also covers a variable that names a token in the
middle, such as AWS_BEARER_TOKEN_BEDROCK, and matches names in any
case. The README states that the harness redacts the files it writes
and does not redact Harbor's own files.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The Bub agent touched the step-failure marker only after removing the tapes, so a failed removal left no marker and the verifier scored the crashed step 1, letting a fail-fast batch run on. The marker now comes first. Harbor writes a literal under a name it does not consider sensitive in full, so the agents' proxy URL, which can carry credentials, reached its job files. The harness now passes the proxy by reference as well, and holds every value it derives for an agent under its own POWERCONTEXT_E2E_AGENT_SECRET_ prefix, which no integration reads back as a native setting and which keeps HTTP_PROXY out of the harness's own environment. A name with _TOKEN_ in the middle that ends in _FILE, _PATH, or _URL, such as AWS_WEB_IDENTITY_TOKEN_FILE, holds where a token is, so its value is no longer redacted from evidence. The Pi failure check reads Pi's output before Harbor downloads the agent's logs. It now stops with an error when the output is not on the host instead of treating the session as an attempt. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
| "agent's logs, which requires an environment that mounts /logs" | ||
| ) | ||
| last: dict[str, Any] = {} | ||
| for line in output.read_text(encoding="utf-8", errors="replace").splitlines(): |
There was a problem hiding this comment.
[P2] Split Pi JSONL records on LF only
Python's splitlines() also splits on U+0085/U+2028/U+2029, which Pi's JSON.stringify(event) leaves unescaped inside valid JSON strings. Using real Pi 0.82.1 and Harbor 0.16.1 with a deterministic test provider, I reproduced a 429 followed by a successful automatic retry whose response contains U+2028. The final message_end is silently discarded, so this raises the stale 429 and excludes the recovered arm as an infrastructure error. Conversely, a terminal error containing U+2028 is missed. Please split on LF only (or iterate over the file's lines), and cover a successful retry with this content so valid message text cannot change the run classification.
str.splitlines also splits at U+2028, U+2029, and U+0085, which Pi's JSON.stringify leaves unescaped inside a string. A message_end record containing one was dropped as invalid JSON, so a retry that recovered from a 429 raised the stale error and a terminal error carrying one was missed. Cover both directions for each separator. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
| assert resolved[name] == proxy_url | ||
| assert "powercontext" in env["NO_PROXY"].split(",") | ||
| # The harness's own requests do not go through the agents' proxy. | ||
| assert not [name for name in os.environ if name.lower() in ("http_proxy", "https_proxy")] |
There was a problem hiding this comment.
[P2] Assert that existing host proxy settings remain unchanged
This assertion fails whenever the process already has HTTP_PROXY, HTTPS_PROXY, or their lowercase variants set. The fixture leaves these variables intact, and _job_config() correctly preserves them. I reproduced the failure with only HTTP_PROXY=http://review-proxy.invalid:3128 set; the same test passes after removing it. This makes make harness-check fail in proxy-enabled development or CI environments even when the agent proxy is forwarded correctly. Please snapshot the host proxy settings before constructing the configuration and assert that they remain unchanged, or explicitly isolate these variables in the test.
The assertion that the harness leaves its own HTTP_PROXY and HTTPS_PROXY alone failed on any host that already sets them. Snapshot the host's settings first and assert they are unchanged instead. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
| """ | ||
|
|
||
| held = f"{_AGENT_SECRET_PREFIX}{name.removeprefix('POWERCONTEXT_')}" | ||
| environ[held] = value |
There was a problem hiding this comment.
[P2] Harness-derived credentials land in the harness process environment, where every child process inherits them
held = f"{_AGENT_SECRET_PREFIX}{name.removeprefix('POWERCONTEXT_')}"
environ[held] = value
return f"${{{held}}}"The reference mechanism itself is the right call and I verified it against harbor==0.16.1: resolve_env_vars only reads os.environ (harbor/utils/env.py:118-119), and templatize_sensitive_env keeps an already-templated value as-is (:68), so the job files never contain the literal. The reason this helper exists is also real — is_sensitive_env_key("PROXY_URL") is False, so without templating the agent proxy URL would be written to the job file in full. Confirmed:
POWERCONTEXT_CLIENT_API_TOKEN sensitive=True
PROXY_URL sensitive=False
POWERCONTEXT_E2E_AGENT_SECRET_PROXY_URL sensitive=True
The part worth tightening is the storage location. environ[held] = value puts every harness-derived credential into the harness process's own environment table, where it is inherited by everything the harness spawns. Measured:
returned reference : ${POWERCONTEXT_E2E_AGENT_SECRET_CLIENT_API_TOKEN}
os.environ holds it: True
child sees plaintext: True
child output : sk-live-ABCDEFGHIJKLMNOPQRST
in evidence_secrets : True
The harness runs git (settings.py:143), docker compose (Harbor passes env=os.environ into the container process), and the plugin install command as subprocesses, so the token is readable from /proc/<pid>/environ or ps e by anything else on the machine, for the lifetime of the run. This is a wider surface than the one the commit message sets out to fix — it moves the value out of Harbor's output directory but not out of reach. The prefix keeps it from being read as an integration's own setting; it does not keep it from being read by unrelated processes.
Suggested direction: hold these values in a process-local store on HarnessSettings rather than os.environ, and inject them into the environment only at the point Harbor resolves the reference (Harbor reads os.environ inside the same process, so a short-lived injection immediately before create_agent_from_config, restored right after, keeps the mechanism working). If the current shape is kept, at minimum document why process-environment visibility is acceptable for these values, since the same runner can be pointed at a real token.
Test worth adding: assert the harness's own os.environ contains none of the derived values after agent_secret returns (today test_job_files_hold_no_part_of_a_short_server_token checks config.model_dump_json() and the native env, but not os.environ), and that a subprocess spawned from the harness does not see the token.
Harbor resolves a reference from the host environment and nowhere else, and every value agent_secret holds derives from a setting that reaches the harness only through that same environment, so a child process of the harness inherits nothing it did not inherit already. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Which issue or RFC does this PR close?
Part of #1705. It fixes defects in the paired harness merged through #1747, #1779, #1800, #1816, #1835, and #1851.
Rationale for this change
A review of the merged harness found three ways its results or evidence could be wrong:
$BUB_HOME/tapes. The harness setsBUB_HOMEinside the container and Harbor does not clear it between the steps of a trial. A recall agent in either arm could read the capture session's prompt from those files and answer without PowerContext. OpenCode (feat(e2e): run paired continuation workloads with OpenCode #1835) and Pi (feat(e2e): run paired continuation workloads with Pi #1851) already clear their own session output; Bub did not.--mode json. In that mode Pi 0.82.1 exits 0 after a failed or aborted model request, so Harbor sees no failure, the verifier finds no answer, and the arm scores 0 and enters the success rate and the paired difference. Pi's text mode exits 1 on the same condition. Codex and Claude Code exit non-zero, and Harbor's OpenCode agent reads OpenCode's error events.harbor-jobs/**/config.jsonand related files. It keeps the first four and last three characters of a literal under a sensitive name, and writes a literal under any other name in full. For the ON arm'sPOWERCONTEXT_BUB_API_TOKENthat is seven characters of the token, and forPOWERCONTEXT_<HOST>_AUTHORIZATIONit is the token's last three. The agents' proxy URL, which can carry credentials, goes underHTTP_PROXYand similar names, which Harbor does not consider sensitive, so it was written in full. The harness does not redact Harbor's files, while the README said every final evidence sink is redacted whatever the token's length.What changes are included in this PR?
"${BUB_HOME:?}/tapes". Bub does not search another session's tape, so this removes only what an agent could read from the files. The removal runs after the agent sets the step-failure marker, which the verifiers read as a failed step, so a failed removal, including an unsetBUB_HOME, fails the step instead of scoring it 1 and letting afail-fastbatch continue.message_endevent in Pi's output. If it is an assistant message that stopped onerrororaborted, the agent raises Harbor'sNonZeroAgentExitCodeError, which the paired command classifies as an error and leaves out of success rates and paired differences. This is the condition Pi's own text mode uses for exit code 1, so a session that failed once and then recovered still counts as an attempt. The check runs before Harbor downloads the agent's logs, so it reads Pi's output through the bind mount of Harbor's Docker environment; when the output is not on the host, the agent stops with an error rather than counting the session as an attempt.${NAME}reference as it is, whatever the name, and resolves it from the harness process's environment when it starts the agent. The harness now holds each value it derives for an agent in its own environment under thePOWERCONTEXT_E2E_AGENT_SECRET_prefix and gives Harbor the reference. The job files record${POWERCONTEXT_E2E_AGENT_SECRET_BUB_API_TOKEN},${POWERCONTEXT_E2E_AGENT_SECRET_<HOST>_AUTHORIZATION}, and${POWERCONTEXT_E2E_AGENT_SECRET_PROXY_URL}, and no part of the values. No integration reads that prefix as a native setting, so later jobs never read a derived value back, andHTTP_PROXYstays out of the harness's own environment. Every variable under the prefix is an evidence secret, which also covers the fullBearer <token>header.AWS_BEARER_TOKEN_BEDROCK, which Harbor's Claude Code agent forwards, and matches names in any case, because settings acceptpowercontext_client_api_token. A plural such asMAX_THINKING_TOKENSstays a count, and a name ending in_FILE,_PATH, or_URL, such asAWS_WEB_IDENTITY_TOKEN_FILE, holds where a token is, so its value stays in the evidence.harbor-jobs/itself.conftest.pygives each harness test its own copy of the process environment, because the harness now writes to it.Are there any user-facing changes?
pairedcommand exit non-zero, instead of counting as a failed attempt.AWS_BEARER_TOKEN_BEDROCKand similar names, and lower-case secret names, are now redacted from the files the harness writes; names such asAWS_WEB_IDENTITY_TOKEN_FILEare not.How was this change tested?
Real runs
Against a local Server with
POWERCONTEXT_SERVER_ACCESS_MODE=enforced, on commit a2bd739:make harness-paired ARGS='--host pi --trials 1',openrouter/z-ai/glm-5.3): OFF 0/1, ON 1/1, no errors or integration failures. Harbor's files hold"POWERCONTEXT_PI_AUTHORIZATION": "${POWERCONTEXT_PI_AUTHORIZATION}"four times and neither the token nor a masked form of it. The ON arm captured and requested context, so Harbor resolved the reference for the agent.OPENROUTER_API_KEY: both arms areerror, the report shows OFF 0/0 and ON 0/0 with one error each, and the command exits non-zero. Before this change the same run scored both arms as failed attempts.make harness-acceptance ARGS='--id project-database-decision'): passed. The tape removal ran in the real container before both steps, and Harbor's files hold"POWERCONTEXT_BUB_API_TOKEN": "${POWERCONTEXT_BUB_API_TOKEN}"and no part of the token.The defects, reproduced
node:22-bookwormcontainer with Pi 0.82.1 and an invalid key,pi --print --mode jsonexits 0 with"stopReason":"error"and a 401 message, andpi --printexits 1. With the same invalid key, Claude Code 2.1.284 and Codex 0.153.4 exit 1.Zq7Zq7Zq7xgaveZq7Z****q7xfor Bub andBear****q7xfor the plugin hosts before this change, and the${NAME}references after it.KEY|SECRET|TOKEN|PASSWORD|CREDENTIAL|AUTH, whichHTTP_PROXYdoes not match, so a literal proxy URL is serialized in full.FileTapeStore(directory=bub.home / "tapes")) and by running that store locally withBUB_HOMEset, which wrote the prompt text totapes/*.jsonl.Checks
make check,make harness-check, andmake unit-testpass.HTTP_PROXYorHTTPS_PROXY.bashagainst a seeded Bub home, for both arms: the earlier tape is gone when the session starts. WithoutBUB_HOME, the removal fails, Bub does not start, and the step-failure marker is in place.AWS_BEARER_TOKEN_BEDROCKand a lower-casepowercontext_client_api_tokenare redacted in every final evidence file, and neither aMAX_THINKING_TOKENScount nor the paths inAWS_WEB_IDENTITY_TOKEN_FILEandHF_TOKEN_PATHrewrite the evidence.Limits
POWERCONTEXT_E2E_AGENT_SECRET_names, the proxy reference, the marker order, and the stricter Pi output check. Unit tests cover those; a real run would record the prefixed references in Harbor's files.harbor-jobs/can contain arbitrary command output, such as an ON agent printing its environment. Design note (non-blocking): the harness could redact that directory after each job, at the cost of rewriting Harbor's output.POWERCONTEXT_E2E_AGENT_SECRET_. Nothing else in the harness process reads that prefix, and the OFF arm's agent environment stays empty./logs/agent/powercontext-step-failedand the ACP agent ID still carry the name PowerContext. Six acceptance verifiers with pinned checksums read that marker, and neither gives access to anything.AI usage statement
This PR was developed with Claude Code (Claude Fable 5.1 and Claude Opus 5.5). Independent Claude review sessions found the defects; Claude verified each one, wrote the fixes and tests, and ran the checks and the real runs above. The author requested the reviews, approved the scope of the fixes, reviewed the change, and provided the run environment.
🤖 Generated with Claude Code