Skip to content

fix: end the null harness cleanly on context overflow - #2456

Merged
mikasenghaas merged 3 commits into
mainfrom
fix/null-harness-overlong
Aug 27, 2026
Merged

fix: end the null harness cleanly on context overflow#2456
mikasenghaas merged 3 commits into
mainfrom
fix/null-harness-overlong

Conversation

@mikasenghaas

@mikasenghaas mikasenghaas commented Aug 27, 2026

Copy link
Copy Markdown
Member

Summary

  • catch a context-overflow error in the null harness program and return cleanly instead of crashing
  • trust a clean harness completion: the rollout no longer re-raises a stashed model-call error after the program exits successfully
  • a program that dies on the error still surfaces it through the harness exception path, and the failed call stays recorded on the trace either way

Follow-up to #2453: the null harness has no compaction, so the relayed overlong prompt error killed its program and failed the rollout.

Breaking

  • A harness program that completes cleanly after a failed model call now produces an ok trace (the failure stays visible on the call record). Previously the rollout failed with the stashed error even on a clean exit.

Verification

  • uv run pytest -q tests/v1 — passed; live E2E tests skipped without PRIME_API_KEY
  • uv run ruff check / uv run ruff format --check

E2E repro against a local vLLM (vllm serve Qwen/Qwen3-0.6B --max-model-len 4096 --enable-auto-tool-choice --tool-call-parser hermes), one dummy task whose toolset returns a ~49KB payload so the turn after the tool call exceeds the 4k window (one-off fixture, not part of this PR):

rollout trace
before (main) failed: ProviderError: upstream 400: This model's maximum context length is 4096 tokens… ok=false, stop_condition=error
after (this PR) completed ok=true, stop_condition=agent_completed, the overlong call still recorded with its ProviderError on trace.calls

Same task against the OpenAI API (gpt-5.6-luna, reasoning_effort="none"), payload ~1.2M tokens: the API rejects with 400 code="context_length_exceeded": "Input tokens exceed the configured limit of 922000 tokens…" — the program catches it and the rollout completes with ok=true, stop_condition=agent_completed, the failed call recorded on trace.calls.

Same task against OpenRouter directly (openai/gpt-4o-mini, payload ~500k tokens): the 400 wraps the upstream error but relays its raw body ("This model's maximum context length is 128000 tokens…", provider_error_code: context_length_exceeded) — caught, rollout completes with ok=true, stop_condition=agent_completed.

Also probed deepseek/deepseek-v4-flash on Prime Inference with the same task at growing payloads:

  • a 706k-token prompt is accepted (the run completes normally, ok=true), so the window is large;
  • past the window (~1M tokens and up), the gateway rejects with a generic 400 "Invalid request." that carries no context-length markers — the harness correctly treats it as a non-overflow provider error, re-raises, and the rollout fails with the real ProviderError. The clean-exit path cannot trigger on this provider until it relays the upstream context-length message (this also blinds every marker-based overflow detector: our loops, pi/prime-agent, Codex).

🤖 Generated with Claude Code


Note

Medium Risk
Behavior change: clean harness exit after a model-call failure now yields an ok trace instead of failing the rollout; marker-based overflow detection can miss generic provider 400s without length wording.

Overview
Context overflow is now treated as a normal budget stop for the null harness (which has no compaction): model BadRequestErrors are classified via provider-specific markers in is_context_overflow, and matching failures exit the program instead of crashing.

Rollout behavior changes when the harness finishes successfully after a failed model call: the driver no longer re-raises the stashed session.error, so the trace can be ok with the failure still on the call record. Harnesses that raise on the error still fail the rollout through the existing exception path.

RolloutSession.error docs are updated to describe this split.

Reviewed by Cursor Bugbot for commit 426b36e. Bugbot is set up for automated code reviews on this repo. Configure here.

Note

Summary

  • catch a context-overflow error in the null harness program and return cleanly instead of crashing
  • trust a clean harness completion: the rollout no longer re-raises a stashed model-call error after the program exits successfully
  • a program that dies on the error still surfaces it through the harness exception path, and the failed call stays recorded on the trace either way

Follow-up to #2453: the null harness has no compaction, so the relayed overlong prompt error killed its program and failed the rollout.

Breaking

  • A harness program that completes cleanly after a failed model call now produces an ok trace (the failure stays visible on the call record). Previously the rollout failed with the stashed error even on a clean exit.

Verification

  • uv run pytest -q tests/v1 — passed; live E2E tests skipped without PRIME_API_KEY
  • uv run ruff check / uv run ruff format --check

E2E repro against a local vLLM (vllm serve Qwen/Qwen3-0.6B --max-model-len 4096 --enable-auto-tool-choice --tool-call-parser hermes), one dummy task whose toolset returns a ~49KB payload so the turn after the tool call exceeds the 4k window (one-off fixture, not part of this PR):

rollout trace
before (main) failed: ProviderError: upstream 400: This model's maximum context length is 4096 tokens… ok=false, stop_condition=error
after (this PR) completed ok=true, stop_condition=agent_completed, the overlong call still recorded with its ProviderError on trace.calls

Same task against the OpenAI API (gpt-5.6-luna, reasoning_effort="none"), payload ~1.2M tokens: the API rejects with 400 code="context_length_exceeded": "Input tokens exceed the configured limit of 922000 tokens…" — the program catches it and the rollout completes with ok=true, stop_condition=agent_completed, the failed call recorded on trace.calls.

Same task against OpenRouter directly (openai/gpt-4o-mini, payload ~500k tokens): the 400 wraps the upstream error but relays its raw body ("This model's maximum context length is 128000 tokens…", provider_error_code: context_length_exceeded) — caught, rollout completes with ok=true, stop_condition=agent_completed.

Also probed deepseek/deepseek-v4-flash on Prime Inference with the same task at growing payloads:

  • a 706k-token prompt is accepted (the run completes normally, ok=true), so the window is large;
  • past the window (~1M tokens and up), the gateway rejects with a generic 400 "Invalid request." that carries no context-length markers — the harness correctly treats it as a non-overflow provider error, re-raises, and the rollout fails with the real ProviderError. The clean-exit path cannot trigger on this provider until it relays the upstream context-length message (this also blinds every marker-based overflow detector: our loops, pi/prime-agent, Codex).

🤖 Generated with Claude Code


[!NOTE]
Medium Risk
Behavior change: clean harness exit after a model-call failure now yields an ok trace instead of failing the rollout; marker-based overflow detection can miss generic provider 400s without length wording.

Overview
Context overflow is now treated as a normal budget stop for the null harness (which has no compaction): model BadRequestErrors are classified via provider-specific markers in is_context_overflow, and matching failures exit the program instead of crashing.

Rollout behavior changes when the harness finishes successfully after a failed model call: the driver no longer re-raises the stashed session.error, so the trace can be ok with the failure still on the call record. Harnesses that raise on the error still fail the rollout through the existing exception path.

RolloutSession.error docs are updated to describe this split.

Reviewed by Cursor Bugbot for commit 426b36e. Bugbot is set up for automated code reviews on this repo. Configure here.

Changes since #2456 opened

  • Updated CONTEXT_OVERFLOW_MARKERS constant in the null harness to use provider-specific context overflow detection phrases [426b36e]
  • Changed exception handling from openai.BadRequestError to openai.APIStatusError and added HTTP status code validation (400 or 413) to the is_context_overflow function's overflow detection logic, which now requires both a matching context overflow marker in the error details and one of the specified status codes [f00bf5d]

The null harness has no compaction, so a relayed overlong prompt
error killed its program and failed the rollout. Catch the overflow
and return instead, ending the run with what the conversation
holds. For the trace to stay ok the rollout must trust that exit:
stop re-raising a stashed model-call error after a clean harness
completion - a program that dies on it still surfaces the original
error through the harness exception path, and the failed call
stays recorded on the trace either way.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@mikasenghaas
mikasenghaas marked this pull request as ready for review August 27, 2026 20:04
@macroscopeapp

macroscopeapp Bot commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Not approved

Macroscope's review found this PR not approvable — The PR fixes context-overflow termination in the null harness, but also changes shared rollout semantics so clean harness exits after model-call failures can produce successful, scored traces. That broader production behavior change warrants focused human review.

You can add or adjust custom eligibility rules. Learn more.

Comment thread verifiers/v1/harnesses/null/program.py
Each marker now names the API whose error wording it matches, and
the unattributable generics are gone - "too many tokens" also
matches Bedrock throttling, and bare "context length"/"context
window" substrings matched more than they targeted.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 426b36e. Configure here.

Comment thread verifiers/v1/harnesses/null/program.py
The 413 markers could never fire: a 413 arrives as a plain
APIStatusError, not BadRequestError. Catch APIStatusError and gate
overflow detection on a deterministic status (400 or 413) so
marker-shaped text in a transient failure never ends the run.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@mikasenghaas
mikasenghaas requested a review from hallerite August 27, 2026 22:00
@mikasenghaas
mikasenghaas merged commit 79d13da into main Aug 27, 2026
13 checks passed
@mikasenghaas
mikasenghaas deleted the fix/null-harness-overlong branch August 27, 2026 22:03
mikasenghaas pushed a commit that referenced this pull request Aug 28, 2026
## Summary

- clear a handled model-call error when a harness segment exits cleanly
- prevent that stale provider error from replacing a later segment's
real failure

## Why

PR #2456 lets a harness complete cleanly after handling a relayed
model-call failure, but the resolved error remained in
`RolloutSession.error`. An interactive segment can then fail before its
next model request resets that slot, causing `Rollout.step()` to record
the stale provider error instead of the new request-interceptor or
harness failure.

The reset happens only after `HarnessSession.turn()` succeeds, so a
harness crash in the current segment still records the intercepted
provider error.

## Verification

- exact two-segment regression probe: handled overflow followed by
request-interceptor failure
- current-segment probe: harness failure still records the provider
error
- `uv run pytest tests/ -q`
- `uv run ruff check --fix .`
- `uv run ty check verifiers`
- `uv run pre-commit run --files verifiers/v1/rollout.py`

The all-files pre-commit run retains the existing MD033 failures in the
unchanged legacy SWE README; the changed-file hooks pass.

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Low Risk**
> Single-line lifecycle fix in rollout stepping with targeted regression
tests; no auth or data-path changes.
> 
> **Overview**
> After a harness segment finishes **without** raising,
**`Rollout.step()`** now sets **`self._session.error = None`**, so a
provider failure that was handled in-segment (e.g. context overflow)
does not linger on the session.
> 
> That stale slot previously could win in the **`step()`** exception
path (**`real = self._session.error`**) when a later segment failed
before the next model call cleared it—masking the new interceptor or
harness error. Failures in the **current** segment are unchanged: the
clear runs only after **`turn()`** succeeds, so a harness crash still
surfaces the stashed provider error.
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
b61b045. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

<!-- Macroscope's pull request summary starts here -->
<!-- Macroscope will only edit the content between these invisible
markers, and the markers themselves will not be visible in the GitHub
rendered markdown. -->
<!-- If you delete either of the start / end markers from your PR's
description, Macroscope will append its summary at the bottom of the
description. -->
> [!NOTE]
> ### Clear `self._session.error` after segment completion in `Rollout`
> Resets the session error state to `None` after a segment finishes and
after agent time remaining and deadline are updated. No other logic or
return conditions were changed.
>
> <!-- Macroscope's review summary starts here -->
>
> <sup><a href="https://app.macroscope.com">Macroscope</a> summarized
b61b045.</sup>
> <!-- Macroscope's review summary ends here -->
>
<!-- Macroscope's pull request summary ends here -->
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.

2 participants