Skip to content

Judge tests never reach a model or the shared queue - #491

Open
EdbertChan wants to merge 9 commits into
mainfrom
plan/judge-tests-never-reach-a-model-or-the-shared-queue
Open

Judge tests never reach a model or the shared queue#491
EdbertChan wants to merge 9 commits into
mainfrom
plan/judge-tests-never-reach-a-model-or-the-shared-queue

Conversation

@EdbertChan

@EdbertChan EdbertChan commented Sep 12, 2026

Copy link
Copy Markdown
Owner

Summary

Judge-calling hook tests now use a shared base that redirects state to a temporary directory and replaces model runners with a local stub.

The full test runner applies the same isolation, while the coverage gate detects judge-importing hooks whose tests omit the shared base.

This prevents unit tests from making model calls or writing jobs into the user's shared judge queue.

Review Claim

Judge tests are isolated by construction and cannot reach a model or the shared queue.

Review Lane

behavior

Review Unit

engine-runtime

Safety Invariant

Test-only and gate-only. No hook decision code changes, and the judge library's runtime behavior outside tests remains untouched.

Slice Rationale

The shared test base, suite-wide isolation, and coverage requirement land together because each closes a different path to the same unsafe test behavior.

Non-goals

  • No hook decision logic changes.
  • No judge runtime default changes.
  • No phrase-list changes.
  • No CI workflow changes.

Architecture

Test isolation moves from a per-file convention to shared enforcement at two boundaries.

Before

graph TD
    A["Judge-calling tests"] --> B["Per-file isolation convention"]
    B --> C["Judge state and runners"]
    D["Suite runner"] --> C
Loading

After

graph TD
    A["Judge-calling tests"] --> B["Shared test base"]
    B --> C["Temporary state and local stub runner"]
    D["Suite runner"] --> C
    E["Coverage gate"] --> F["Requires shared base reference"]
    A --> F
Loading

Test Plan

Test Plan
  • python3 -m unittest discover -s engine/hooks/llm-judge/tests && python3 -m unittest discover -s engine/hooks/wrong-check-reflect/tests && python3 -m unittest discover -s engine/hooks/diu-stop/tests — 60, 16, and 104 tests passed; each runner reported OK.
  • python3 scripts/check_hook_test_coverage.pycheck_hook_test_coverage: OK (36 hook(s) checked).
  • bash scripts/scrub-handoff-artifacts.shscrub-handoff-artifacts-ok.
  • python3 scripts/check_no_new_comments.py --base origin/mainok no new comments.
  • python3 engine/skills/make-pr/scripts/preflight.py --base origin/mainok preflight passed.

Revert Plan

Revert Plan
  • Safe to revert? Yes.
  • Revert command: git revert <merge-commit-sha>.
  • Post-revert steps: Re-run the judge-hook test suites and hook-coverage gate.
  • Data migration? No.

Note

Low Risk
Test infrastructure and coverage gates only; production hook and judge paths are unchanged.

Overview
Introduces a shared JudgeTestCase base in engine/hooks/llm-judge/testing.py so judge-related tests always use a temporary state directory and a local stub runner instead of real model backends or the user’s default judge queue.

Hook and llm-judge test suites are refactored to inherit that base (replacing ad hoc patch.dict / duplicate temp dirs). test_judge.py adds a regression that enqueue only writes under the test state dir, and default-runner-order coverage clears RUNNERS_ENV explicitly.

scripts/run_all_tests.sh exports the same isolated state + stub runners for the full unittest discover run. scripts/check_hook_test_coverage.py fails hooks that import judge but whose tests never reference JudgeTestCase.

No changes to hook decision logic or judge runtime behavior outside tests.

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

EdbertChan and others added 9 commits September 12, 2026 07:06
…: a shared test base redirects the judge's state directory and runners, and every judge-calling hook's tests use it instead of their own hand-rolled isolation.

Review lane: behavior
Safety invariant: Test support code only. No hook decision logic changes and the judge library keeps its runtime defaults.
Effectiveness measurement: A new test asserts an enqueue inside the base class writes a job file under the temporary directory while the judge's default cache path stays empty.
Slice rationale: The base class and its adopters, alone. The suite runner and the coverage gate are the next task because the lint keeps repo-root scripts in their own task.
Architectural effect: Test isolation moves from per-file convention into a shared base class that every judge-calling hook's tests inherit.
Goal: Make it impossible for a hook's tests to call a model or write into the shared queue.
Motivation: Real model calls and shared-queue writes happened during a unit test run.
Alternative considerations: Leaving isolation to each test file was rejected: that is the convention that failed. Making the judge refuse to run outside a temporary directory was rejected as a runtime change that would affect live sessions.
Implementation details: Read engine/hooks/llm-judge/judge.py for the environment variable names for the state directory and the runner list and for the runner entry shape, then read engine/hooks/llm-judge/tests/test_judge.py and engine/hooks/wrong-check-reflect/tests/test_hooks.py for the isolation already in use. Create engine/hooks/llm-judge/testing.py exporting a unittest.TestCase subclass whose setUp creates a temporary directory, points the state directory at it, and sets the runner list to one local stub command printing a single JSON line; tearDown restores the prior environment and removes the directory. Point the judge-calling tests under engine/hooks/llm-judge/tests, engine/hooks/wrong-check-reflect/tests and engine/hooks/diu-stop/tests at that base, and add one test asserting the enqueue lands in the temporary directory and not in the default cache path.
Non-goals: No hook decision logic, no judge runtime defaults, no phrase list changes, and no repo-root script changes in this task.
Layer: domain
Feature state: active
Files:
- engine/hooks/llm-judge/testing.py
- engine/hooks/llm-judge/tests/test_judge.py
- engine/hooks/wrong-check-reflect/tests/test_hooks.py
- engine/hooks/diu-stop/tests/test_plain_words_check.py
Change types:
- engine/hooks/llm-judge/testing.py: create
- engine/hooks/llm-judge/tests/test_judge.py: modify
- engine/hooks/wrong-check-reflect/tests/test_hooks.py: modify
- engine/hooks/diu-stop/tests/test_plain_words_check.py: modify
Acceptance criteria:
- `python3 -m unittest discover -s engine/hooks/llm-judge/tests && python3 -m unittest discover -s engine/hooks/diu-stop/tests` exits 0.
- `python3 scripts/check_no_new_comments.py --base origin/main` exits 0.

Exit code: 0
Invoker-Finalize-Id: 839172f2-58a6-40f2-87fa-46c6eec5aa42
…the judge-calling hooks' test suites pass under the shared base.

Review lane: proof
Safety invariant: Verification is read-only and changes no file.
Effectiveness measurement: The unittest runs are the measurement.
Slice rationale: One proof for the base class and its adopters.
Architectural effect: None; verification only.
Goal: Prove the isolated suites pass.
Motivation: Running them is the only evidence the base works.
Alternative considerations: The whole suite was rejected as slower without adding evidence for this claim.
Implementation details: Run the three judge-calling hooks' suites.
Non-goals: No edits.
Layer: app_regression
Feature state: active
Acceptance criteria:
- Exits 0 only when all three suites pass.

Exit code: 0
Invoker-Finalize-Id: f561c0e4-2e8b-4fef-b066-e6ce7e40c648
…iew claim: the suite runner isolates the judge's state directory and runners for the whole run, and the hook-coverage gate fails a judge-importing hook whose tests do not use the shared base.

Review lane: policy
Safety invariant: Tooling only. The runner sets environment variables for its own run and the gate only reads files; no hook or library code changes.
Effectiveness measurement: Running the gate against a judge-importing hook whose tests lack the base fails and names that hook; the gate passes on the repository once the previous task has landed.
Slice rationale: The two repo-root scripts, kept out of the previous task because the lint refuses policy files beside product files.
Architectural effect: Isolation gains a second layer at the suite boundary, and the coverage gate gains one requirement.
Goal: Make the suite safe even for a test that forgets the base class, and make the omission visible.
Motivation: The convention was omitted once already, silently.
Alternative considerations: Relying only on the base class was rejected: a new test file can skip it. Putting the check in CI only was rejected: the failure happened on a developer machine.
Implementation details: In scripts/run_all_tests.sh, create a temporary directory and export the judge's state-directory and runner-list variables for the whole run, using the same names engine/hooks/llm-judge/judge.py reads. In scripts/check_hook_test_coverage.py, add a requirement that any hook whose non-test Python imports the judge has at least one test file referencing the shared base, and keep every existing requirement.
Non-goals: No hook or library changes, no new gate file, and no CI workflow edits.
Layer: transport
Feature state: active
Files:
- scripts/run_all_tests.sh
- scripts/check_hook_test_coverage.py
Change types:
- scripts/run_all_tests.sh: modify
- scripts/check_hook_test_coverage.py: modify
Acceptance criteria:
- `python3 scripts/check_hook_test_coverage.py` exits 0.
- `python3 scripts/check_no_new_comments.py --base origin/main` exits 0.

Exit code: 0
… widened hook-coverage gate passes on this repository.

Review lane: proof
Safety invariant: Verification is read-only and changes no file.
Effectiveness measurement: The gate's exit code is the measurement.
Slice rationale: One proof for the gate change.
Architectural effect: None; verification only.
Goal: Prove the gate passes with its new requirement.
Motivation: A gate that fails on its own repository is not landable.
Alternative considerations: Running it for one hook only was rejected: the new requirement applies to every judge-importing hook.
Implementation details: Run the hook-coverage gate over every hook.
Non-goals: No edits.
Layer: app_regression
Feature state: active
Acceptance criteria:
- Exits 0 only when the gate passes for every hook.

Exit code: 0
Invoker-Finalize-Id: 4a591ddd-22fe-4980-92a7-8600c3829a0b
…no ephemeral handoff files remain in the worktree.

Review lane: cleanup
Safety invariant: The scrub script only checks for known handoff artifact names and never touches source or tests.
Effectiveness measurement: The script exits non-zero if any handoff artifact remains.
Slice rationale: Required terminal scrub for every implementation workflow.
Architectural effect: None; hygiene only.
Goal: Leave the branch free of handoff artifacts.
Motivation: Handoff files must not reach the PR.
Alternative considerations: Manual cleanup was rejected as non-deterministic.
Implementation details: Run scripts/scrub-handoff-artifacts.sh.
Non-goals: No product edits.
Layer: app_regression
Feature state: active
Acceptance criteria:
- `bash scripts/scrub-handoff-artifacts.sh` exits 0.

Exit code: 0
Invoker-Finalize-Id: 06a0b20e-8df4-4247-a41c-990186826c56
…abec587ad-e41fdc0f — Review claim: no ephemeral handoff files remain in the worktree.

Review lane: cleanup
Safety invariant: The scrub script only checks for known handoff artifact names and never touches source or tests.
Effectiveness measurement: The script exits non-zero if any handoff artifact remains.
Slice rationale: Required terminal scrub for every implementation workflow.
Architectural effect: None; hygiene only.
Goal: Leave the branch free of handoff artifacts.
Motivation: Handoff files must not reach the PR.
Alternative considerations: Manual cleanup was rejected as non-deterministic.
Implementation details: Run scripts/scrub-handoff-artifacts.sh.
Non-goals: No product edits.
Layer: app_regression
Feature state: active
Acceptance criteria:
- `bash scripts/scrub-handoff-artifacts.sh` exits 0.
@cursor

cursor Bot commented Sep 12, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: serverGenReqId_3409cad3-0950-49d9-a987-a560a2d3754f)

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