Skip to content

feat(harbor): upload standard verifier output - #724

Merged
Abhijeet Prasad (AbhiPrasad) merged 5 commits into
mainfrom
codex/harbor-verifier-evidence
Sep 1, 2026
Merged

feat(harbor): upload standard verifier output#724
Abhijeet Prasad (AbhiPrasad) merged 5 commits into
mainfrom
codex/harbor-verifier-evidence

Conversation

@AbhiPrasad

@AbhiPrasad Abhijeet Prasad (AbhiPrasad) commented Aug 31, 2026

Copy link
Copy Markdown
Member

Supersedes #716

AI Summary

Braintrust already received the reward numbers for each Harbor trial. Now it also receives the evidence the verifier wrote, so you can see why a trial failed without leaving the UI.

<trial-dir>/verifier/
├── reward-details.json   already uploaded
├── ctrf.json             NEW  standard test-report JSON
├── test-stdout.txt       NEW  raw verifier log
└── test-stderr.txt       NEW  raw verifier log

Verifier evidence shows up in two places: a bounded inline preview and one complete normalized verifier-output.json attachment.

root (experiment row)
├── task
│   └── verification    ← verifier_output_summary   (inline preview)
│                         verifier_output           (full evidence, attached)
└── scorer: reward      ← same two fields

Attachment modes

Harbor runs are evaluation environments, where complete failure evidence is generally more useful than omitting raw logs. The default therefore captures all standard verifier output.

attachments= ctrf.json test-stdout/stderr.txt task artifacts
none
structured
all (default) ✅ (with artifact_include)

structured is the JSON-only tier for environments that should not upload raw verifier logs. Task artifacts remain opt-in even under all: they require an explicit artifact_include glob.

Structured redaction matches sensitive key names, while plain text has no keys:

# ctrf.json — the key is named ANTHROPIC_API_KEY, so the value is caught
{"env": {"ANTHROPIC_API_KEY": "[REDACTED]"}}

# test-stdout.txt — plain text is covered only by configured patterns
"+ export ANTHROPIC_API_KEY=sk-ant-REAL-SECRET"

If an eval environment contains sensitive values, configure redact_patterns or use attachments="structured". redact_patterns is the only redaction applied to raw verifier logs and is empty by default.

Size limits are gone

There used to be a 5 MB per-file cap and a 20 MB per-trial cap. The per-trial cap was one shared pot, spent in source order:

artifacts ──► trajectory images ──► verifier output ──► reward details
^ spends first                                         ^ often got nothing left

That meant a few screenshots could push out the score evidence, and the same trial with a slightly larger screenshot could upload different evidence. Attachments are intended for large payloads, so both attachment caps and their shared-budget plumbing are removed and every accepted file uploads whole.

trajectory.json keeps a separate limit (max_trajectory_bytes, 20 MB) because it is parsed in-process rather than treated as an attachment.

Configuration changes

  • attachments="all" is now the default.
  • attachments="verifier-details" is renamed to attachments="structured".
  • max_attachment_bytes and max_total_attachment_bytes are removed and no longer impose an upload ceiling.

David Leen (dleen) and others added 3 commits August 28, 2026 12:06
`max_total_attachment_bytes` was one shared pot consumed in source order, so artifacts and
trajectory images could starve verifier output and reward-details. Same trial, one bigger
screenshot, different evidence. Attachments are meant to be large and `Attachment` uploads lazily
from a path, so both caps and the budget plumbing are gone; `_read_bounded_file` becomes
`_read_safe_file`, keeping the symlink, file-type and dev/ino checks. `trajectory.json` is parsed
in-process rather than uploaded, so it keeps a bound of its own via `max_trajectory_bytes`.

Two fixes from the same review:

- Raw `test-stdout.txt`/`test-stderr.txt` now require `attachments=all`. `normalize_json` redacts by
  key name, which raw text lacks, so `redact_patterns` — empty by default — was the only protection
  on a payload the default config uploaded. `ctrf.json` stays in `verifier-details`.
- `json.loads` raises `RecursionError`, not `JSONDecodeError`, on deeply nested input. A 400 KB
  `ctrf.json` aborted `_sync_final_result` after the spans started but before output, scores and
  metadata, leaving a half-written row. Both decoders now fall back to text.

Tests use the real span harness (`init_test_exp` + `with_memory_logger`) instead of `RecordingSpan`
fakes, so they assert the stored `AttachmentReference` and the queued upload.

BREAKING CHANGE: `max_attachment_bytes` and `max_total_attachment_bytes` are removed, and unknown
options are silently ignored, so a config setting them now uploads with no ceiling.
`verifier-details` no longer ships raw verifier stdout/stderr; use `attachments=all` with
`redact_patterns`.
Follow-up to 3306c05d, from a quality pass over the branch.

`RecursionError` from `json.loads` was guarded at two call sites but not on task-written
`manifest.json` or either `trajectory.json` reader. The guard is now `identity.try_parse_json`,
used at all five. Those readers also moved to `read_bytes`, closing a second hole: `read_text`
raises `UnicodeDecodeError`, a `ValueError`, which the old `except (OSError, JSONDecodeError)`
never caught either.

Span previews used `normalize_json`'s default `max_depth=8` while their attachment used 20, so a
12-deep `ctrf.json` came out `[DROPPED: depth limit]` in the span and whole in the attachment. That
default was incidental. `_bounded_summary` now passes 20 for both verifier output and reward
details, and skips the walk when the payload already fits — its serialized size is known from the
attachment, so re-walking and re-measuring a multi-MB payload bought nothing.
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

The verifier-output tests wrote raw stdout/stderr fixtures with
Path.write_text, which translates "\n" to "\r\n" in text mode on
Windows. The plugin decodes the file's bytes verbatim, so the summary
carried CRLF and the assertions failed on Windows only. Write those
fixtures as bytes so the on-disk content matches what is asserted.

Also skip the fifo parametrization of the unsafe-file-type test where
os.mkfifo does not exist.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@AbhiPrasad
Abhijeet Prasad (AbhiPrasad) merged commit 87d6465 into main Sep 1, 2026
83 checks passed
@AbhiPrasad
Abhijeet Prasad (AbhiPrasad) deleted the codex/harbor-verifier-evidence branch September 1, 2026 15:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants