mirror of
https://github.com/Comfy-Org/github-workflows.git
synced 2026-11-05 00:17:37 +00:00
[PR #188] [MERGED] feat(coderabbit-config): flag unknown keys inside schema-open objects (BE-7786) #198
Labels
No labels
cursor-review-catalog-drift
groom
groom
pull-request
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
Comfy-Org/github-workflows#198
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
📋 Pull Request Information
Original PR: https://github.com/Comfy-Org/github-workflows/pull/188
Author: @mattmillerai
Created: 8/20/2026
Status: ✅ Merged
Merged: 8/20/2026
Merged by: @mattmillerai
Base:
main← Head:matt/be-7786-coderabbit-config-unknown-keys📝 Commits (3)
4b011a4feat(coderabbit-config): flag unknown keys inside schema-open objects (BE-7786)72aa76dfix(coderabbit-config): cap findings after fan-out, name keys by source spelling, bound list traversal (BE-7786)350dd93docs(coderabbit-config): document the partial-scan severity row; correct a stale test comment (BE-7786)📊 Changes
6 files changed (+823 additions, -44 deletions)
View changed files
📝
.github/coderabbit-config/README.md(+45 -11)📝
.github/coderabbit-config/check_coderabbit_config.py(+300 -23)📝
.github/coderabbit-config/tests/test_check_coderabbit_config.py(+395 -2)📝
.github/workflows/test-coderabbit-config-validate.yml(+30 -0)📝
AGENTS.md(+2 -2)📝
docs/callers/coderabbit-config-validate.md(+51 -6)📄 Description
ELI-5
CodeRabbit throws away any key in
.coderabbit.yamlit doesn't recognize. The checker only told you about that in five places — the handful of spots where the schema explicitly says "nothing else allowed here". Everywhere else it said nothing, because the JSON Schema library it leans on had no rule to trip. Soreviews: {profil: chill}— one letter off, and every setting under it silently inert — came back clean, in both normal and strict mode.This makes the check look at the other 103 objects too. It reports the same thing, the same way, in the same words; it just no longer waits for the schema to spell out a rule it can restate.
The rule, and why it is drawn this way
Walking the parsed config alongside the schema, an object is treated as closed by omission only when it declares a
propertiesblock and none ofadditionalProperties,unevaluatedProperties,patternProperties,anyOf,oneOf,allOf,$ref,$dynamicRef,if,dependentSchemas,propertyNames,not. Each of those is a way an object can legitimately hold a name itspropertiesdoes not list, so reading any of them as "closed" would invent a warning on a valid config — the failure mode that matters most here, because this check gates other repos' CI. Today's schema uses exactly one of the twelve, so eleven entries are pure future-proofing against a vendored-schema bump.The walk descends only through matched properties and through
items; never into a combinator branch, anadditionalPropertiesschema, or the value under a key it just reported (that key has no subschema, so nothing beneath it is judgeable). Where the document and the schema disagree about shape it stays silent — that's a type error and jsonschema already describes it, in better words than a second complaint would.No double-reporting, by construction rather than by de-duplication. jsonschema fires only where the keyword is present; the walk only where it is absent. Complements, not overlapping sets.
test_the_two_halves_never_report_the_same_keyexercises both at once on one config so a future refactor that merged them fails here instead of double-annotating someone's PR.That last claim is stated as a fact about this vendored schema, not as a property of the two conditions — they are complements at a single node, but jsonschema evaluates a document path against every applicable subschema, so a node this walk judges closed whose
$reftarget or combinator branch carriedadditionalProperties: falsewould be reported by both halves.OpennessTestpins the two premises that rule it out today: the schema carries no$ref, and no node declaringpropertiesalso carries a combinator (so such a node counts as OPEN here and is never judged closed).Findings from both halves go through one builder, so severity (
warning, orerrorunderstrict_unknown_keys), message text, path, YAML line and the "did you mean" hint are identical by construction. They shareMAX_FINDINGS, and the truncation note fires on the combined total — including the case where a singleadditionalProperties: falseerror fans out into more findings than the cap, which theisliceoniter_errorsdoes not bound. The walk carries its own budget, charged per key inspected and per node visited: the document is PR-controlled, and while PyYAML builds an anchored mapping once, an aliased one is walkable as a far larger tree, including through lists, which name no key at all. A walk that stops early — on the budget or the depth cap — reports that as a finding at the unknown-key severity, so it is a warning by default and a failure understrict_unknown_keys: a scan that could not rule out unknown keys must not hand back a green check to a repo that asked for unknown keys to fail.Verification, against the artifacts the ticket names
The reported miss, reproduced first. On
origin/main,reviews: {profil: chill},reviews.tools.golangci-lint.enabld, andreviews.tools.github-checks.timeout_mseach returned zero findings in default and strict mode. After the change each returns exactly one, at the right path and the right line.Acceptance, verbatim. A config containing only
reviews.tools.github-checks.timeout_mswarns and exits 0 by default; exits 1 under--strict-unknown-keys.Suites. coderabbit-config 72 tests pass (45 pre-existing, unchanged — none asserted the old miss — plus 27 added across the original change and the review round). All 8 existing CLI smoke steps from the test workflow, executed verbatim, pass; a 9th is added for this class. Also green: workflow-pins (78 + the lint itself, 10 workflows clean), agents-md-integrity (46), cursor-review (194), groom (365), refresh-reviewers (48),
shellcheckon the bumper scripts.Sized against the real corpus, not a fixture. I swept 194 non-archived org repos read-only; 20 carry a
.coderabbit.yaml. Across all 20, findings go from 1 → 15. Every one of the 14 new findings is the same key,reviews.tools.github-checks.timeout_ms— i.e. zero false positives on real-world configs, which is the number that matters for a check other repos' CI depends on. All 20 still exit 0 in default mode, so enrolling this turns nothing red; 15 would exit 1 understrict_unknown_keys, which is opt-in and is exactly what the new rollout note in the caller doc warns about. Repo names are withheld — most are private and this repo is public; of the six public ones,ComfyUI,comfy-python-sdk,comfy-api-proxy,comfy-typescript-sdkandComfyUI_prevcarry the stale key.The "stale key" claim, falsified rather than assumed. I fetched the live upstream schema and checked it directly:
reviews.tools.github-checksnames onlyenabled, declares noadditionalProperties, and upstream today has the same 5-explicit / 103-by-omission split as the vendored copy. The repo's ownschema_drift.pyagainst live upstream reports no property ormaxLengthchanges — the drift is cosmetic and touches nothing this walk's behaviour depends on. (Moving the vendored pin isrefresh-coderabbit-schema.yml's job, not this PR's.)Judgment calls
The shared finding builder changes one pre-existing behaviour, deliberately. A non-string YAML key is now named by its source spelling, read off the composed key node, on both paths. Previously such a key rendered through
_format_pathas a list index ([1]for1: x) and the line lookup compared an int against the composed node's string value and returned no line. An intermediatestr()coercion fixed the list-index half but not the rest:safe_loadresolves plaintrue/on/yestoTrue,nulltoNoneand0x10to16, so those keys were annotatedreviews.True/reviews.None/reviews.16— paths naming a key that appears nowhere in the file — and still lost the line, because the lookup then compared"True"against the node's"true". Matching key nodes tag-aware (constructing them the waysafe_loaddid, which also keeps a quoted"true"from matching the boolean) fixes the name and the line together.str()survives only as the fallback for a key whose node cannot be found, e.g. an aliased or merge-keyed mapping.The "did you mean" hint stays silent rather than guessing. It was written for the specific-name case, where the one other place the schema uses a name is almost certainly the intended home — the root-level
tools:block. Keys found by the by-omission walk are dominated by generic names (enabledsits at ~72 places in the schema, plusscope,path,instructions), for which the alphabetically-first three are three wrong homes. The shortlist now prefers candidates under the offending key's own parent prefix and suppresses the hint past three remaining candidates; the motivatingtools:→reviews.toolscase is pinned by a test so the suppression cannot swallow it.Two new tests pin schema facts (5 open / 103 closed-by-omission, and that only
additionalPropertiesis used today). They will fail on a schema refresh that changes either. That is intentional and matches the existingSchemaTestconvention in this file — a refresh that redefines what this check means should say so out loud in its own PR.Bound by key-inspections and node-visits, not one or the other. A node-only budget is the obvious choice and the wrong one: one visit to a mapping with 100k keys costs 100k, so a node cap alone leaves the work superlinear in an adversarial document. But a key-only budget leaves list traversal outside the bound entirely — elements recurse without naming a key, and an alias-amplified nested list can charge 1 for an outer entry while thousands of element visits below it are free. Charging both is what makes the bound cover the aliased-list shape. The "did not see the whole document" flag is set where the walk actually bails rather than derived from the leftover budget afterwards, so an exact landing on the budget is a complete walk and a depth cutoff is a partial one; stopping on the findings cap is neither, and is reported as truncation instead. The depth cap remains belt-and-braces — descent follows the schema, which is finite and shallow, and
$refis an opener this walk never resolves.Residual, and what stays open
reviews.tools.github-checks.timeout_msare not cleaned by this PR. Each is a one-line edit in a different repository; this PR is scoped to one repo. They warn (exit 0) once enrolled, and the caller doc now tells an operator to clear them before switching onstrict_unknown_keys.CODERABBIT_CONFIG_CALLERSroster is a repo secret by design and is not readable from here. The org-wide numbers above are the upper bound on impact, not the enrolled count.AGENTS.mdis 273 lines, over the 200-line ceiling its own standard enforces. Pre-existing onmain(273 there too) and untouched by this change — my edit is deliberately net-zero on line count. This repo does not self-enrollagents-md-integrity.yml, so nothing catches it in CI. Out of scope to fix here, but it is a real finding on a file this change touches.strict_unknown_keysis the switch and the default should flip. This change widens where that premise applies, so it widens the blast radius of it being wrong.Refs BE-7786 — the ticket stays open: the stale keys it names as evidence remain in the org's repos, unfixed by this PR, and the enrolled-caller count is unverifiable from here.
Provenance
main(so no semantic conflict with the base); the test workflow's CLI smoke steps executed verbatim, all pass; agents-md-integrity 46 passed; cursor-review 194 passed; groom 365 passed (1 skipped); each of the eight panel findings reproduced against the pre-fix checker before being fixed and re-run after; worst-case key-lookup timing measured against the pre-fix build (no regression). Carried from the authoring run and not re-run here, since this round touches no workflow or shell file: workflow-pins 78 passed +check_workflow_pins.pyclean (10 workflows); refresh-reviewers 48 passed;shellcheck -xonbump-callers.sh+preflight.shclean; checker run read-only over 20 real org.coderabbit.yamlfiles (all exit 0 default; 1 → 15 findings, all 14 new ones the same genuine stale key)str(), and the walk budget covers node visits as well as keys — and the prose above has been corrected to match rather than left describing the first round.🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.