Skip to content

A check that cannot run no longer reports success - #768

Merged
jorgemanrubia merged 3 commits into
mainfrom
evals-ci-honest
Sep 19, 2026
Merged

jorgemanrubia merged 3 commits into
mainfrom
evals-ci-honest

Conversation

@jorgemanrubia

Copy link
Copy Markdown
Member

The Skill Evals job has been passing without running a single eval. It needs ANTHROPIC_API_KEY, and this repository has none — no repository secret, no organization secret, and fork pull requests never receive one. The job noticed, printed a warning, and exited 0. So every file under skill-evals/ carried a green tick that meant nothing had been checked, which is worse than no check at all: it occupies the place a reader looks for assurance.

Originally tracked in The Skill Evals job reports success without running a case.

The second half of the same defect is a comment. The guard added in The connector runs where it is started: no directory is a project's is honest that it models the shape of a --serve value and nothing else — the mock, expect_sequence and accept_response patterns are read by the Ruby runner under Onigmo, and a Go test compiling with RE2 cannot speak for them even where it reads them. A malformed pattern in any of those three lands with CI green.

Which way the first half goes

A skip, not a failure. The key is genuinely unavailable here, and fork pull requests are structurally unable to receive one, so failing every pull request that touches a skill would produce a red everyone learns to click past. The job should say what it means instead.

A step-level if cannot do that — it leaves the job green. So the job is split: a preflight decides whether the evals can run, and Skill Evals runs only on its say-so. When a pull request changes a skill or its evals and the key is absent, the job now reports Skipped rather than success, and the preflight emits a warning annotation and a step summary naming how many cases were not run and what does still cover them. test.yml is in the paths filter, so this pull request demonstrates it on itself.

Skill Evals is not a required check, so nothing is blocked by it going grey.

Closing the second half

scripts/check-eval-patterns.rb compiles every pattern in every case file under the engine that reads them, run from make check and from the Integration Tests job. It does not run the evals. It catches the pattern that does not compile, which is what was landing unnoticed: break a mocks pattern in a connector case and the Go guard still passes while this one fails.

Its own floors are the assertion rather than setup for it. No case files found, no patterns compiled at all, or a runner that compiles more pattern kinds than the checker models are each a failure — a checker that has quietly fallen behind the runner would otherwise report success over the keys it still happens to know.

The Skill Evals job exited 0 when ANTHROPIC_API_KEY was unset, which it is
here: no repository secret, no organization secret, and fork pull requests
never receive one. Every eval file was covered by a green tick that meant
nothing had run.

The key is genuinely unavailable, so this is a skip rather than a failure —
failing every pull request that touches a skill would be noise nobody reads.
But a step-level `if` leaves the job green, so the job is split: a preflight
decides whether the evals can run, and Skill Evals now reports Skipped when
they cannot, with a warning annotation and a step summary naming how many
cases were not run. test.yml is in the paths filter, so this pull request
proves it on itself.

And the guard in connect_skilleval_test.go named a limitation it could not
enforce: the mock, expect_sequence and accept_response patterns are read by
the Ruby runner under Onigmo, and a Go test compiling with RE2 cannot speak
for them. scripts/check-eval-patterns.rb closes it from the other side — it
compiles every pattern in every case file under Onigmo itself, run from
make check and from the Integration Tests job.

It does not run the evals. It catches the malformed pattern that was landing
unnoticed: a broken mock in a connector case passes the Go guard today and
fails this one. Its own floors are the assertion rather than setup for it —
no case files found, no patterns compiled, or a runner that compiles more
pattern kinds than this checker models are each a failure, because a checker
that has fallen behind reports success over the keys it still knows.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Copilot AI balanced review requested due to automatic review settings September 19, 2026 07:12
@github-actions

Copy link
Copy Markdown

Sensitive Change Detection (shadow mode)

This PR modifies control-plane files:

  • .github/workflows/test.yml

Shadow mode — this check is informational only. When activated, changes to these paths will require approval from a maintainer.

@github-actions github-actions Bot added commands CLI command implementations tests Tests (unit and e2e) ci CI/CD workflows labels Sep 19, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot review overview

🟡 Changes recommended

The preflight lacks required pull-request permissions, and malformed case structures can pass validation.

Get a fresh assessment by requesting another Copilot review.

Review effort: Balanced
Findings: 1 High severity · 1 Medium severity

Open (2)
What changed in this PR

Makes skill-eval CI accurately report when evaluations cannot run and adds Ruby-native regex validation.

Changes:

  • Adds a preflight gate for honest skipped status.
  • Compiles eval patterns under Onigmo during integration checks.
  • Clarifies the Go guard’s limited scope.

[!TIP]
If you aren't ready for review, convert to a draft PR.
Click "Convert to draft" or run gh pr ready --undo.
Click "Ready for review" or run gh pr ready to reengage.

File Description
scripts/​check-eval-patterns.rb Adds Ruby pattern validation.
Makefile Integrates validation into make check.
internal/​commands/​connect_skilleval_test.go Documents test coverage boundaries.
.github/​workflows/​test.yml Adds validation and the eval preflight gate.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread .github/workflows/test.yml
Comment thread scripts/check-eval-patterns.rb Outdated
The preflight declared contents: read only, while dorny/paths-filter
enumerates the pull request's files through the REST API. It works on this
repository because the repository is public, which is incidental rather than
a reason. Granted pull-requests: read, matching the other path-filter job in
this workflow.

The worse half of that finding is what the preflight does when the filter
cannot answer. At this pinned version the action fails the step rather than
returning an empty set, so the gate goes red rather than quietly deciding
nothing changed — but the gate should not depend on that. It now refuses any
answer that is not true or false: a preflight that cannot tell must not be
allowed to say no, which is this pull request's own defect one level up.

And the pattern checker normalized malformed cases into compilable ones. A
scalar accept: became a one-item list and compiled; a string where a mock
belongs was compiled as if it were the mock's pattern. Both pass a checker
that coerces and crash the runner, which calls .each on the first and indexes
the second as a mapping — NoMethodError and TypeError respectively, confirmed
against ruby before the fix. Shape is now checked before content, with a nil
value still allowed because the runner reads these as (c[key] || []).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings September 19, 2026 07:30

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot review overview

🔵 Needs a closer look

The documented mise and Nix environments do not provide the newly mandatory Ruby runtime.

Review effort: Balanced
Findings: None

Resolved since last review (2)
Previously missed (1)

In code that hasn't changed since last review

Medium severity Add Ruby to documented mise and Nix development environments

Makefile:432

check now always runs check-eval-patterns, but neither documented development setup provides Ruby. bin/setup installs only the tools in .mise.toml, which has no Ruby entry, and the Nix shell package list in flake.nix:31-42 also omits it despite CONTRIBUTING.md:21 saying the shell provides every bin/ci tool. A contributor starting from either supported setup will therefore fail the standard local CI gate at this target. Add a pinned Ruby version to the mise and Nix environments and document it in the requirements.

make check now runs check-eval-patterns on every invocation, and bin/ci is
make check. Neither .mise.toml nor the flake's dev shell provided Ruby, so
CONTRIBUTING's "the development shell provides Go and the tools required by
bin/ci" stopped being true the moment that target was added — a contributor
following the documented setup exactly would fail the standard local gate and
read it as their machine being wrong.

Pinned at 3.3 in both, matching the ruby/setup-ruby pins in test.yml, with the
sync comment .mise.toml keeps for every other pin. Verified rather than
assumed: mise install fetches 3.3.12 and the check runs under it.

The flake side could not be verified the same way — there is no nix on this
machine — and it turned out nothing verified it anywhere either, because the
nix job builds packages.default and never looks at the dev shell. A misspelled
attribute in that list lands green and breaks nix develop for whoever follows
CONTRIBUTING, which is this pull request's subject in another file. The job now
evaluates the dev shell derivation, which resolves every attribute in seconds
without building any of them. ruby_3_3 exists at the nixpkgs revision
flake.lock pins, read from that revision directly.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings September 19, 2026 07:43
@jorgemanrubia

Copy link
Copy Markdown
Member Author

🤖 Correct, and it is my change that made CONTRIBUTING.md:21 false — bin/ci is make check, and make check now needs Ruby. Fixed in 61aade9: ruby = "3.3" in .mise.toml with the sync comment that file keeps for every other pin, ruby_3_3 in the flake's dev shell, and a line in Requirements.

Verified rather than asserted, and the two halves are not equally verified:

  • mise: mise install ruby@3.3 fetched 3.3.12 here and mise exec -- ruby scripts/check-eval-patterns.rb ran clean under it. That one I ran.
  • nix: there is no nix on this machine, so I did not run nix develop and am not claiming I did. What I did check is that ruby_3_3 exists at the exact nixpkgs revision flake.lock pins (c27cdad), by reading pkgs/development/interpreters/ruby/default.nix and pkgs/top-level/all-packages.nix at that revision.

That gap was worth more than a disclaimer, because nothing else was checking it either: the nix job runs nix build, which builds packages.default and never touches the dev shell, so a misspelled attribute in that package list would land green and break nix develop for whoever followed CONTRIBUTING. Same shape as this pull request, one file over. The job now also evaluates the dev shell derivation, which resolves every attribute without building any of them — seconds, and it turns a list that was merely written down into one that is checked. It runs on this pull request, so the flake half is verified by CI rather than by me.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot review overview

🔵 Needs a closer look

The preflight currently prevents existing push and manual Skill Eval executions.

Review effort: Balanced
Findings: None

Previously missed (2)

In code that hasn't changed since last review

Medium severity Preflight skips evals on push and manual triggers

.github/​workflows/​test.yml:255

This condition skips the preflight on both push to main and workflow_dispatch, even though both remain top-level workflow triggers. Because skill-eval needs this job and only runs when its run output is true, the eval job is then skipped unconditionally on those events. Previously it could run after its path filter, so even configuring ANTHROPIC_API_KEY would no longer enable post-merge or manual eval runs. Preserve these trigger paths and make the preflight's change detection event-aware.

Medium severity Drift guard validates pattern count but not pattern identity

scripts/​check-eval-patterns.rb:37

The drift guard compares only the number of Regexp.new calls with six. If a runner change removes one modeled pattern site and adds a new case-file pattern key in the same edit, the count remains six; this checker continues scanning the removed key, ignores the new one, and can pass a malformed regex. Verify the identity of each modeled runner site, or share the pattern schema with the runner, rather than checking cardinality alone.

@jorgemanrubia

Copy link
Copy Markdown
Member Author

🤖 On the "Preflight skips evals on push and manual triggers" note — the regression half of this is not right, and I checked before acting on it.

skill-eval on main already carries if: github.event_name == 'pull_request' (.github/workflows/test.yml:239). Evals have never run on push or workflow_dispatch, with or without ANTHROPIC_API_KEY. Under this change the preflight inherits that same condition, so on those events the gate is skipped, its run output is empty, and skill-eval is skipped — the identical outcome to today, reached one job later.

So nothing about post-merge or manual eval runs is lost here, because there was nothing there to lose.

The underlying observation is still worth recording, just not as a regression: the evals cannot run on push or manually at all, and that predates this change. Whether they should is a real question — a post-merge eval run would catch a case file that only breaks once merged — but it is a scope decision about when evals run, not about whether a job lies when it cannot run. That is this change's subject, and it is settled: Skill Evals now reports Skipped instead of Success when it cannot execute.

@jorgemanrubia
jorgemanrubia merged commit b1e3be3 into main Sep 19, 2026
27 checks passed
@jorgemanrubia
jorgemanrubia deleted the evals-ci-honest branch September 19, 2026 08:04
@jorgemanrubia

Copy link
Copy Markdown
Member Author

🤖 Two findings in the latest review. One is right, one is not.

Preflight skips evals on push and manual triggers — not correct, and worth being precise about. The finding says "previously it could run after its path filter" on push and workflow_dispatch. It could not. origin/main's skill-eval job carried if: github.event_name == 'pull_request' at line 239, immediately above the checkout, and that condition is the one I moved to the preflight verbatim. Skill Evals has never run on a push to main or on a manual dispatch, before this pull request or after it — git show origin/main:.github/workflows/test.yml has it. Behaviour on those two triggers is unchanged, so there is no path to preserve here. Whether the evals ought to run post-merge is a real question, but it is a different change and it was not true before.

The drift guard checks cardinality, not identity — correct, and it was the weakest thing in the file. A runner edit that drops one pattern key and adds another in the same commit leaves Regexp.new at six, and the checker would go on compiling the key that no longer exists while never seeing the new one. A guard reporting success over input it stopped examining, which is what this whole pull request is about.

Fixed in 149a4e5. The checker now reads which case keys the runner actually takes and requires that set to equal the keys it models, with the keys that carry no pattern (context, file, max_commands, tags, task) declared by name rather than inferred — so a key the runner starts reading has to be classified by someone, and the failure when it is not is this check going red. Both directions fail: a pattern key the checker does not compile, and a modelled key the runner no longer reads. The count check stays, because it still catches a second pattern field added to a key already modelled, which leaves the key set unchanged.

Proved by breaking the runner both ways before trusting it:

$ # swap reject_response for deny_response — count still 6
$ ruby scripts/check-eval-patterns.rb
eval patterns: 2 problem(s)
  skill-evals/run: reads c["deny_response"], which this checker does not compile — add it to STRING_LISTS or MATCH_LISTS, or to NON_PATTERN_KEYS if it carries no pattern
  skill-evals/run: no longer reads c["reject_response"], but this checker still compiles it

jorgemanrubia added a commit that referenced this pull request Sep 19, 2026
Comparing the runner's Regexp.new count to six answers cardinality and
nothing else. A runner edit that drops one pattern key and adds another in
the same commit leaves the count at six, and the checker would go on
compiling the key that no longer exists while never seeing the new one — a
guard reporting success over input it stopped examining, which is the defect
the check itself exists to remove.

It now reads which case keys the runner takes and requires that set to equal
the keys modelled here, with the keys that carry no pattern declared by name
rather than inferred. Both directions fail: a pattern key the checker does
not compile, and a modelled key the runner no longer reads. The count check
stays, because it still catches a second pattern field added to a key already
modelled, which leaves the key set unchanged.

Proved by breaking the runner both ways: swapping reject_response for another
key keeps the count at six and fails on identity, and adding a seventh key
fails on both.

Copilot raised this on #768; the pull request merged before the fix landed,
so it follows here.

Co-authored-by: Claude Opus 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

ci CI/CD workflows commands CLI command implementations docs tests Tests (unit and e2e)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants