Skip to content

Sweep fixes: speaker eval shared DB, lab qa-bench exit codes, Sparkle smoke stale report - #2101

Closed
r3dbars wants to merge 6 commits into
mainfrom
claude/sweep-fix-tools
Closed

r3dbars wants to merge 6 commits into
mainfrom
claude/sweep-fix-tools

Conversation

@r3dbars

@r3dbars r3dbars commented Oct 7, 2026 •

Copy link
Copy Markdown
Owner

Three confirmed bugs from the Haiku sweep, all in internal tools. Test first each time. The first 3 commits are the original fixes; the next 2 fix review round 1, and the last one fixes round 2.

Speaker lab: shared speaker DB leaked between runs

meeting-series kept <work>/shared-db forever, so a --force rerun (or a fresh run over a work root an earlier run used) started with every voice the last run learned. fresh_db=false meetings then matched people they were supposed to meet for the first time, and lab_result.json / naming rows looked better than they were.

  • Fix (MeetingLabSharedDB.swift): before the first meeting, prepare() reads a lab-shared-db.json marker in shared-db.
    • The marker records the set, work root, ordered shared meetings and DB file name. It also records every meeting-series flag except --only/--limit/--force (flag order doesn't matter), and sha256 of the --embedder-thresholds file and the TRANSCRIPTED_LAB_KNOBS_FILE knobs file.
    • Before each shared meeting can write voices, the marker gets inProgress: <id>. recordApplied() clears it and adds the meeting once its lab_result.json is written.
  • How a run decides:
    • It resumes only when there's no --force, the marker matches this run, nothing is marked in progress, and the meetings in the DB are exactly the finished ones (first N in series order, no gap).
    • It starts empty on a brand-new run (nothing finished) or with --force. It only deletes after checking the path is under the run's own work root (refuses .. escapes and the root itself).
    • It stops and asks for --force, leaving the DB untouched, for any other mismatch while finished meetings exist. Examples: a meeting killed mid-run, a failed mid-series meeting retried after later ones finished, a different --work, changed flags (--calendar-naming, --separation, --sep-*, --speaker-hint, --embedder-*), edited thresholds or knobs files, or a DB with no marker. The error says why. It doesn't silently run the rest of the series on an empty DB.
    • Every run logs resumed or starting empty (<reason>).
  • Behavior change to know: --force --only m3 now runs m3 against an empty shared DB, not one holding m1/m2's voices. To rerun one mid-series meeting properly, rerun the series from the start with --force.
  • Test: runMeetingLabSharedDBSelfTests() in speaker-eval-harness autoeval-self-test. It drives prepare/beginMeeting/recordApplied/fingerprintArgs/fileHashes against real files in a temp work root, and lists every failed promise. The package has no XCTest target.
    • Each round went red first, then green. Round 2's red listed 14 broken promises against the round-1 code, including the interrupted meeting, the refusals without --force, --calendar-naming, and the thresholds/knobs hashes.
    • I also broke the config check, the in-order check, the in-progress check, the in-progress clear and the path guard on purpose; each went red.
  • This self-test only runs by hand. The test-matrix command is BLOCKED by agent-check.py, which won't run a bare-path command (already true on main). SpeakerEvalHarness is also in TOOLS_WITHOUT_CI, so check.sh and CI don't cover it. I ran it by hand on this head: PASS. The runMeetingSeries call sites (prepare before the loop, beginMeeting before the pipeline, recordApplied after lab_result.json, die on refusal) aren't covered by any test, because they need Parakeet and the diarizer models.

Lab: qa-bench exit 1/3 never got scored

transcripted-qa-bench.sh's write_report writes qa-report.md and results.tsv, then returns 1 on any FAIL row and 3 on any SKIP or hard-WARN row. --skip-build always records a SKIP, so it always exits 3. LabExperimentRunner treated any nonzero exit as a dead process and returned before QAResultsAnalyzer ran, so the run showed as Failed with no breakdown.

  • Fix: for the QA and transcription-corpus benches, an exit of 1 or 3 gets analyzed only when it came from write_report.
    • qa-report.md must exist. results.tsv alone proves nothing, because the script truncates it at startup and can exit 1 elsewhere (cd "$REPO_ROOT" || exit 1, set -u aborts).
    • An exit 1 also needs at least one FAIL row.
    • FAIL rows still become hard gates, so the run is still Failed, now with the breakdown. Exit 3 adds a warning, so it shows Warning, not Passed.
    • Timeouts, other exit codes, and anything that didn't reach write_report still fail like before.
  • Tests (QABenchExitCodeTests, a fake repo with a fake qa-bench script):
    • testQABenchHoldExitStillScoresSkippedRun, testQABenchHoldExitIsNotReportedAsAPass, testQABenchFailExitKeepsStepBreakdown: red ("Experiment process exited with code 3.") then green.
    • Review round: testQABenchAbortAfterPassingStepsStillFails (was passed), testQABenchAbortWithEmptyResultsStillFails (was warning) and testQABenchFailExitWithoutFailRowsStillFails (was passed) went red, then green.
    • testQABenchCrashWithoutReportStillFails and testQABenchUnknownExitStillFailsEvenWithReport guard the failure side.

QA: Sparkle smoke passed on a stale report

runScenario never removed launch-ui-smoke.json from an earlier run, and the default --output is a fixed /tmp path. An app that crashed before reporting would decode the old report and PASS.

  • Fix: delete the old report before launching.
  • Tests: testScenarioDoesNotPassOnLaunchReportLeftByEarlierRun (fake app script that writes nothing; was PASS, now fails on launch-report) and testScenarioPassesOnLaunchReportWrittenByThisLaunch (a fresh report still counts). runScenario went from private to internal so the test can drive it without the native-smoke isolation gate. The fake app is a shell script, never the real app.

Checks

  • swift test --package-path Tools/TranscriptedLab --jobs 4: 16 tests pass
  • Both Lab product builds and Tools/TranscriptedLab/script/build_and_run.sh --verify (run by hand): pass
  • swift test --package-path Tools/TranscriptedQA: 94 tests pass
  • bash build-deps.sh --force, swift build --package-path Tools/SpeakerEvalHarness, speaker-eval-harness autoeval-self-test (run by hand): PASS
  • bash check.sh: everything else passed. It reports two BLOCKED (invalid-command): build_and_run.sh --verify and speaker-eval-harness autoeval-self-test. That's agent-check.py refusing bare-path matrix commands, already true on main. Neither one runs in check.sh; I ran both by hand.

Independent review: changes requested (round 2), fixes pushed, re-review pending

🤖 Generated with Claude Code

transcripted-qa-bench.sh writes its report and then exits 1 when a step
FAILed or 3 when a step was skipped or held (always the case with
--skip-build). LabExperimentRunner treated any nonzero exit as a failed
process and never ran QAResultsAnalyzer, so those runs lost their whole
breakdown. Now exit 1/3 with a results.tsv on disk gets analyzed (FAIL rows
still become hard gates, exit 3 adds a warning); timeouts, other exit codes,
and runs with no results still fail as before.
runScenario never removed launch-ui-smoke.json from a previous run, and
the default --output is a fixed /tmp path, so an app that crashed before
reporting would decode the old report and PASS. Remove it before the
launch so only this run's report counts.
meeting-series kept <work>/shared-db across runs, so a --force rerun (or
a fresh run over a work root an earlier run used) started with every voice
the last run learned, and fresh_db=false meetings silently matched people
they should have met for the first time. Now only a plain resume (no
--force, some shared-DB meeting already finished) keeps it; every other
run clears it, and only after checking the path is under the run's own
work root. Promise checks run in autoeval-self-test.
results.tsv exists from the script's first line, so it can't prove
write_report ran. A set -u abort after a few PASS steps, or the
cd "$REPO_ROOT" || exit 1 failure, exits 1 with a partial results file
and got scored as passed or warning. Now exit 1/3 counts only when
qa-report.md exists, and exit 1 also needs a FAIL row.
Keying resume off lab_result.json alone could still keep a polluted DB:
retrying a failed mid-series meeting after later ones finished, resuming
with a different --work, or switching --embedder-* (a different DB file).
prepare() now writes lab-shared-db.json into shared-db with the set, work
root, ordered shared meetings, backend, DB file, and --embedder-* flags,
and recordApplied() appends each shared meeting as it finishes. A run
resumes only when the marker matches and the meetings in the DB are
exactly the finished ones, in series order with no gap; otherwise it
starts empty. Every run logs whether it resumed or reset and why.
Review round 2 on the shared-DB marker:
- The marker now records the shared meeting in progress before the
  pipeline can write voices, and clears it when lab_result.json lands.
  A run killed mid-meeting no longer resumes on its partial voices.
- When the DB can't be resumed but finished meetings exist and --force
  isn't set, meeting-series stops with the reason and asks for --force
  instead of running the rest of the series on an empty DB.
- The fingerprint covers every flag except --only/--limit/--force
  (order-free) plus sha256 of the --embedder-thresholds file and the
  TRANSCRIPTED_LAB_KNOBS_FILE knobs file, so --calendar-naming,
  --separation, --sep-*, --no-invite and --speaker-hint changes count.
@r3dbars

r3dbars commented Oct 7, 2026

Copy link
Copy Markdown
Owner Author

Split into one draft PR per bug, at Justin's request. Same reviewed code (approved with nits after 3 review rounds here):

Closing this one; branch claude/sweep-fix-tools is kept.

@r3dbars r3dbars closed this Oct 7, 2026
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.

1 participant