Skip to content

scripts: keep the qualification harness within its open-file limit (P1R-C06) - #113

Merged
novelKR merged 1 commit into
mainfrom
claude/p1r-c06-qualification-report-k7gur1
Oct 17, 2026
Merged

novelKR merged 1 commit into
mainfrom
claude/p1r-c06-qualification-report-k7gur1

Conversation

@novelKR

@novelKR novelKR commented Oct 17, 2026 •

Copy link
Copy Markdown
Owner

Issue

Part of #101: P1R-C06. The first recovery rehearsal on the qualification host failed in the harness itself, so this fixes the harness before the candidate is frozen again. It follows #112 and does not close an issue.

Item Value
Base main at b2cb042 (the merge of #112)
Failed run p1r-c06-recovery-rehearsal-2026-10-17-b2cb042 on the qualification host, MANIFEST.json SHA-256 5f58673c69f35c993852953daf4d5a09ff0dbaf2c857a7fca343aeb507fac631; verify accepts it (recorded harness_failed, recomputed failed)
Pins Codex gitlink with its recorded patch, and DevGuard 6e7e065; both unchanged

Summary

Before: the harness kept a descriptor of everything it started until the run ended: each gateway's three pipes, the transcript the evidence writer opened for it, and each fixture authority's output. Run from Terminal, whose default limit is 256 open files, the recovery rehearsal ran out at its 27th functional scenario (recovery-fence … concurrent-gateways): that scenario and every later one failed with OSError: [Errno 24] Too many open files in the harness's own Popen, the idle interval of the measurement passed once the functional writer had closed its files, and a journal read at the load interval's drain then failed with unable to open database file and ended the run as harness_failed. The morning's checks of the same suites passed because the session that ran them had a limit of 1,048,576. No failure came from CodeSpace or DevGuard.

After: the harness stays far below 256 open files however long it runs (at most 43 in a whole rehearsal), and a recovery --rehearsal passes the same way under a limit of 256 as without one. A journal that cannot be read at the drain makes the interval inconclusive, as the protocol says, instead of ending the run, and every report records the open-file limit the harness and what it started ran under.

How.

  • Gateways (scripts/qualify_host.py): the reader of a gateway's output closes its output and input pipes once the connection ends, and the reader of its log closes its error pipe. end_gateway, Gateway.close and the recovery bench's teardown wait for both readers through Gateway.join_readers, so the pipes are closed when they return.
  • Fixture authorities: the reader closes the output pipe when it ends; kill and stop close the input and the log and wait for the reader.
  • Evidence writer: it keeps at most 32 files open (WRITER_OPEN_FILES), closes the least recently written first, and appends again when it opens one anew. A governed recovery interval writes about 17 files at once, so it closes none of them; the functional scenarios' writer, which gets one transcript per gateway, no longer keeps them all.
  • Worker logs (scripts/qualify-devguard.py): the parent's copies of the standalone and managed workers' logs are closed once the workers have started.
  • The drain: an OSError or sqlite3.Error while it waits for the Runner's instance to retire is kept as journal_error and adds "the journal could not be read" to the interval's invalid reasons.
  • Evidence: harness.open_files_limit in each report.json holds the soft and hard limits.

The protocol freezes the harness with it; this change comes before the qualification run, and the candidate is frozen again on its merge, so every qualification run uses it.

Contract changes

None. The product is unchanged; only the harness and its tests change. Each report.json gains harness.open_files_limit, which verify does not compare (same_harness compares the harness files only).

Tests

Verified here at the head of this branch, in a Linux cloud container, with debug builds of codespace-mcp and codespace-codex-runtime (the product is unchanged since b2cb042):

  • python3 -B -m unittest discover -s scripts/tests: 122 passed, 5 of them new: ended gateways leave none of their pipes open, whether their input closed, SIGTERM, SIGUSR1, SIGKILL, a kill at teardown or close ended them, while each stays referenced as a run keeps it; stopped and killed fixture authorities likewise; the writer never holds more than its limit and appends every row in order across reopening; a journal that cannot be opened at the drain is recorded as the interval's invalid reason, the error the qualification host hit; and the report records the limit. Without the fix the fixture test fails with 14 descriptors left instead of 5, and the drain test fails with the host's OperationalError.

  • pyflakes on the four scripts.

  • recovery --rehearsal --participation off under a soft limit of 256 (ulimit -Sn 256), with the harness's descriptors sampled every 2 seconds:

    Harness Functional (34 off) Idle Load Run, verify Most descriptors
    b2cb042, before 24 passed, then all 10 from the 25th (recovery-reconcile … kill-during-exec) failed with Errno 24 passed inconclusive failed (exit 1), verified 249
    this branch 34 passed, 2 gaps not run passed inconclusive inconclusive (exit 2), verified 43

    The load interval is inconclusive by design here ("no load ran: there is no DevGuard service to run it"), as in the rehearsal of scripts, docs: the independent Runner's qualification harness and protocol (P1R-C06) #112. This branch's report records open_files_limit [256, 20000] and the harness commit 4297da4, not dirty; its MANIFEST.json SHA-256 is 9b6e9b355fac9c1892d8116bf5b0bbf8508ee751294cd0f8d95c430a148a32c7.

  • Without a limit, the unfixed harness held 22 descriptors after the first functional scenario and 429 after the last.

Not verified here: required, which runs only on macOS, and the measurement against the installed service. On the merge, the qualification host repeats the check suites under a limit of 256 before the candidate is frozen again.

Security scenarios

  • Credentials: unchanged; the harness still passes only the credential file's path and reads no credential.
  • Rollback honesty: an interval whose journal cannot be read is inconclusive and says why; it is never counted as passed, and the run goes on to its other intervals and repetitions.

Out of scope

  • The qualification run and its report, which follow on the candidate frozen at this merge.
  • Raising the open-file limit for the harness or the processes it starts; they run under the limit they are given, which the report records.

…1R-C06)

The recovery rehearsal on the qualification host ran out of open files under
Terminal's default limit of 256: every gateway left its three pipes and its
transcript open for the rest of the run, so the 27th functional scenario and
every later one failed with "Too many open files", and a journal read at the
load interval's drain then ended the run.

- The readers of a gateway's output and log close its pipes when it ends, and
  end_gateway, close and the recovery bench's teardown wait for them.
- The fixture authority's reader closes its output; kill and stop close its
  input and log and wait for the reader.
- The evidence writer keeps at most 32 files open, closing the least recently
  written first and appending again when it reopens one. A recovery interval
  writes about 17 files at once, so it closes none of them.
- The parent's copies of two worker logs are closed after the start.
- A journal that cannot be read at the drain makes the interval inconclusive,
  as the protocol says, instead of ending the run.
- Each report records the open-file limit the harness and what it started ran
  under.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
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