Skip to content

Fix-forward PR 2306 (doc-gate config errors): 2 config-error paths still exit 1 - #2312

Merged
jaylfc merged 4 commits into
devfrom
exec/tsk-aerar5
Aug 9, 2026
Merged

Fix-forward PR 2306 (doc-gate config errors): 2 config-error paths still exit 1#2312
jaylfc merged 4 commits into
devfrom
exec/tsk-aerar5

Conversation

@jaylfc

@jaylfc jaylfc commented Aug 6, 2026

Copy link
Copy Markdown
Owner

CARD TITLE (intent, not commit subject): Fix-forward PR 2306 (doc-gate config errors): 2 config-error paths still exit 1

Autonomous build of board card tsk-aerar5.

Pr #2306 left two config-error paths that still exited 1 instead of the
config-error code:

  • Structurally invalid config (valid TOML, wrong shape, e.g. rules is a
    string) passed load_config and died later in the rule loop with an
    AttributeError. Add _validate_config to check the shape after parsing.

  • Non-UTF-8 config bytes raised UnicodeDecodeError inside tomllib.load
    (which decodes before parsing), uncaught by the TOMLDecodeError-only
    handler. Catch that alongside the other config errors.

Also move EXIT_CONFIG_ERROR from 2 to 3: exit code 2 is argparse's own
usage-error exit, so a typo'd flag was indistinguishable from a broken
config. Now 1 (violation), 2 (argparse), and 3 (config error) are all
mutually distinct.

Supersedes exec/tsk-thh54d (pr #2306).

Files:
scripts/check_doc_gate.py | 48 +++++++++++++++++++++++++++++--
tests/test_doc_gate.py | 72 +++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 117 insertions(+), 3 deletions(-)

Summary by CodeRabbit

  • Bug Fixes

    • Improved configuration validation for missing, malformed, unreadable, or incorrectly structured configuration files.
    • Configuration errors now produce a distinct exit status from document-gate violations and command-line usage errors.
    • Preserved clear status reporting for successful checks and detected violations.
  • Tests

    • Added coverage for invalid configuration formats, encoding issues, missing files, valid violations, and invalid command-line usage.

jaylfc added 3 commits August 5, 2026 04:21
An unparseable or missing doc-gate config previously raised an unhandled
traceback that exited 1 -- identical to a real documentation-drift violation,
so a typo in docs/doc-gate.toml was indistinguishable from a missing
changelog. Catch tomllib.TOMLDecodeError and OSError in main(), print a
clear error to stderr, and exit 2 instead.
…figs

Pr #2306 left two config-error paths that still exited 1 instead of the
config-error code:

- Structurally invalid config (valid TOML, wrong shape, e.g. rules is a
  string) passed load_config and died later in the rule loop with an
  AttributeError.  Add _validate_config to check the shape after parsing.

- Non-UTF-8 config bytes raised UnicodeDecodeError inside tomllib.load
  (which decodes before parsing), uncaught by the TOMLDecodeError-only
  handler.  Catch that alongside the other config errors.

Also move EXIT_CONFIG_ERROR from 2 to 3: exit code 2 is argparse's own
usage-error exit, so a typo'd flag was indistinguishable from a broken
config.  Now 1 (violation), 2 (argparse), and 3 (config error) are all
mutually distinct.

Supersedes exec/tsk-thh54d (pr #2306).
@coderabbitai

coderabbitai Bot commented Aug 6, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The document gate now uses named exit codes, validates the structure of parsed TOML configuration, reports configuration failures, and preserves distinct codes for violations and invalid CLI usage. Tests cover malformed, missing, unreadable, and structurally invalid configurations.

Changes

Document gate configuration errors

Layer / File(s) Summary
Exit-code contract and reporting
scripts/check_doc_gate.py
The script defines named codes for success, gate violations, and configuration errors. Reporting returns the named success and violation codes.
Configuration validation and CLI handling
scripts/check_doc_gate.py, tests/test_doc_gate.py
Configuration validation rejects invalid TOML structures and startup maps loading or validation failures to the configuration-error code. Tests verify codes 1, 2, and 3 for their respective outcomes.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant CLI
  participant TOMLConfiguration
  participant ConfigValidator
  participant stderr
  CLI->>TOMLConfiguration: Load configuration
  TOMLConfiguration-->>CLI: Parsed data or loading error
  CLI->>ConfigValidator: Validate parsed structure
  ConfigValidator-->>CLI: Valid data or validation error
  CLI->>stderr: Report configuration failure
Loading

Possibly related PRs

  • jaylfc/taOS#2306: Both changes distinguish configuration errors from documentation-gate violations in scripts/check_doc_gate.py and its tests.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly identifies the doc-gate configuration error paths and their incorrect exit code, which matches the main changes.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch exec/tsk-aerar5

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@gitar-bot

gitar-bot Bot commented Aug 6, 2026

Copy link
Copy Markdown

Gitar is working

Gitar

@qodo-code-review

qodo-code-review Bot commented Aug 6, 2026

Copy link
Copy Markdown

PR Summary by Qodo

Doc-gate: validate config shape and use distinct exit code for config errors

🐞 Bug fix 🧪 Tests 🕐 20-40 Minutes

Grey Divider

AI Description

• Add structural validation for parsed doc-gate TOML to prevent runtime crashes.
• Treat parse/IO/encoding/shape failures as config errors with a distinct exit code.
• Add regression tests ensuring exit codes distinguish violations, usage errors, and config errors.
Diagram

graph TD
A["check_doc_gate.py main()"] --> B["argparse parse_args"] --> C["load_config()"] --> D{"Config OK?"}
B -- "usage error" --> X(["Exit 2"])
Y[("doc-gate.toml")] --> C
D -- "no" --> E["print config error"] --> F(["Exit 3"])
D -- "yes" --> G["run invariants/rules"] --> H["_report()"] --> I{"Failures?"}
I -- "no" --> J(["Exit 0"])
I -- "yes" --> K(["Exit 1"])
subgraph Legend
direction LR
_p["Process"] ~~~ _d{"Decision"} ~~~ _f[("Config file")] ~~~ _e(["Exit code"])
end
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Introduce a schema/validation library (e.g., pydantic/jsonschema)
  • ➕ More expressive validation (types, required fields, defaults) with clearer error reporting
  • ➕ Easier to evolve config format safely as it grows
  • ➖ Adds a dependency and associated maintenance for a small script
  • ➖ May be overkill for a small, stable config surface
2. Move validation and error normalization into load_config()
  • ➕ Single entry point for all config handling; simpler main()
  • ➕ Encourages reuse if config loading is needed elsewhere
  • ➖ Requires load_config() to know about CLI concerns (stderr messaging/exit semantics) unless carefully layered

Recommendation: The PR’s current approach (lightweight structural validation plus centralized exception handling in main) is the best fit here: it avoids new dependencies, fixes the two remaining exit-1 leak paths (wrong-shape and non-UTF-8), and makes CI outcomes unambiguous by reserving 2 for argparse and using 3 for config errors.

Files changed (2) +117 / -3

Bug fix (1) +45 / -3
check_doc_gate.pyAdd config-shape validation and distinct config-error exit code +45/-3

Add config-shape validation and distinct config-error exit code

• Defines explicit exit codes (0 ok, 1 violation, 3 config error) while leaving argparse’s usage exit at 2. Adds _validate_config() to reject valid-TOML-but-wrong-shape configs and updates main() to catch TOMLDecodeError, UnicodeDecodeError, OSError, and ValueError, reporting a clear stderr message and returning EXIT_CONFIG_ERROR.

scripts/check_doc_gate.py

Tests (1) +72 / -0
test_doc_gate.pyAdd regression tests for config/usage/violation exit-code behavior +72/-0

Add regression tests for config/usage/violation exit-code behavior

• Adds a dedicated test class covering: unparseable TOML, missing config files, structurally invalid configs, non-UTF-8 configs, and bad CLI flags. Verifies that violation (1), argparse usage error (2), and config error (3) remain mutually distinguishable.

tests/test_doc_gate.py

@jaylfc

jaylfc commented Aug 6, 2026

Copy link
Copy Markdown
Owner Author

nemotron-super review

VERDICT: Approved
No blocking issues found.

Automated first-pass review by the nemotron-super lane. The lead still reviews before merge.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@scripts/check_doc_gate.py`:
- Around line 105-121: Extend configuration validation before evaluate_rules and
check_referenced_paths to validate each rule’s consumed fields, including
when_changed and on_modify, using the expected types and rejecting invalid
scalar values; also validate every referenced_paths_scan entry as a string. Keep
existing outer-container checks and raise clear ValueError messages identifying
the invalid rule index or scan entry.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 25077cfe-7b2b-4580-9ca2-0219076a18d7

📥 Commits

Reviewing files that changed from the base of the PR and between 94cbb74 and 654a82b.

📒 Files selected for processing (2)
  • scripts/check_doc_gate.py
  • tests/test_doc_gate.py

Comment thread scripts/check_doc_gate.py
Comment on lines +105 to +121
rules = config.get("rules", [])
if not isinstance(rules, list):
raise ValueError("'rules' must be a list of tables")
for i, rule in enumerate(rules):
if not isinstance(rule, dict):
raise ValueError(f"rules[{i}] must be a table")
gate = config.get("gate", {})
if not isinstance(gate, dict):
raise ValueError("'gate' must be a table")
if "trailer" in gate and not isinstance(gate["trailer"], str):
raise ValueError("'gate.trailer' must be a string")
invariants = config.get("invariants", {})
if not isinstance(invariants, dict):
raise ValueError("'invariants' must be a table")
scan = invariants.get("referenced_paths_scan", [])
if not isinstance(scan, list):
raise ValueError("'invariants.referenced_paths_scan' must be a list")

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

ast-grep outline scripts/check_doc_gate.py --lang python --items all --type function
sed -n '90,250p' scripts/check_doc_gate.py
rg -n -C 4 'def _match_any|def check_referenced_paths|when_changed|require_doc|on_modify|referenced_paths_scan' \
  scripts/check_doc_gate.py tests/test_doc_gate.py

Repository: jaylfc/taOS

Length of output: 16464


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== test file outline/digest =="
wc -l tests/test_doc_gate.py
rg -n 'when_changed|require_doc|on_modify|referenced_paths_scan|_validate_config|valueerror|config error|exception|raises' tests/test_doc_gate.py scripts/check_doc_gate.py
sed -n '1,120p' tests/test_doc_gate.py
sed -n '120,360p' tests/test_doc_gate.py

echo "== behavioral type probe =="
python3 - <<'PY'
from pathlib import Path
import tomllib
import sys
sys.path.insert(0, str(Path(__file__).parent))
from scripts.check_doc_gate import _glob_match, extract_path_tokens, evaluate_rules, check_referenced_paths, _match_any, _validate_config

cases = {
    "rule when_changed scalar": {"rules": [{"name": "r", "when_changed": 7, "require_doc": []}]},
    "rule require_doc scalar": {"rules": [{"name": "r", "when_changed": [], "require_doc": 7}]},
    "rule on_modify string": {"rules": [{"name": "r", "when_changed": [], "require_doc": [], "on_modify": "yes"}]},
    "rule hint number": {"rules": [{"name": "r", "when_changed": [], "require_doc": [], "hint": 7}]},
    "scan entry number": {"invariants": {"referenced_paths_scan": [7]}},
}
for case, config in cases.items():
    print(f"\n{case}")
    try:
        _validate_config(config)
        print("_validate_config: passed")
    except Exception as e:
        print(f"_validate_config: {type(e).__name__}: {e}")
        continue

    if case.startswith("rule"):
        try:
            out = evaluate_rules([("A", "desktop/src/apps/Foo.tsx")], ["msg"], config)
            print(f"evaluate_rules: passed returned {out!r}")
        except Exception as e:
            print(f"evaluate_rules: {type(e).__name__}: {e}")
    else:
        try:
            out = check_referenced_paths(Path(".checkdoc", encoding="utf-8"), ["README.md"], config)
            print(f"check_referenced_paths: passed returned {out!r}")
        except Exception as e:
            print(f"check_referenced_paths: {type(e).__name__}: {e}")

for val in (7, "a/b", "a/b"):
    for f in (_glob_match, extract_path_tokens, _match_any):
        print(f"{f.__name__}({val!r}) -> ", end="")
        try:
            if f is _match_any:
                print(f"{'True' if f('a/b', [val]) else 'False'}")
            else:
                print(f"{f(val)!r}")
        except Exception as e:
            print(f"{type(e).__name__}: {e}")
PY

Repository: jaylfc/taOS

Length of output: 19025


🏁 Script executed:

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
import re
from pathlib import Path

def _glob_match(path, pattern):
    """Reduced version of scripts/check_doc_gate.py._glob_match for type behavior."""
    regex_parts = []
    i = 0
    length = len(pattern)
    while i < length:
        char = pattern[i]
        if char == "*":
            if i + 1 < length and pattern[i + 1] == "*":
                if regex_parts and regex_parts[-1] == "/" and i + 2 == length:
                    regex_parts[-1] = "(?:/.*)?"
                else:
                    regex_parts.append(".*")
                i += 2
            else:
                regex_parts.append("[^/]*")
                i += 1
        elif char == "?":
            regex_parts.append("[^/]")
            i += 1
        else:
            regex_parts.append(re.escape(char))
            i += 1
    return re.fullmatch("".join(regex_parts), path) is not None

def _is_test_path(path):
    """Reduced version of scripts/check_doc_gate.py._is_test_path for type behavior."""
    base = path.rsplit("/", 1)[-1]
    if "/__tests__/" in path:
        return True
    if base.startswith("test_") and base.endswith(".py"):
        return True
    return base.endswith((".test.tsx", ".test.ts", ".test.jsx", ".test.js", ".spec.tsx", ".spec.rs", ".spec.ts", ".spec.jsx"))

def extract_path_tokens(text):
    """Reduced version of scripts/check_doc_gate.py.extract_path_tokens for type behavior."""
    pos = 0
    while pos < len(text):
        if text[pos] == "`":
            start = pos + 1
            pos_end = text.find("`", pos + 1)
            if pos_end == -1:
                break
            token = text[start:pos_end]
            if not any(c in token for c in "*?"):
                yield token
            pos = pos_end + 1
        else:
            pos += 1

values = [7, None, {}, [], True, "a/b"]
for f in (_glob_match, _match_any := lambda path, patterns: any(_glob_match(path, pat) for pat in patterns), _is_test_path, extract_path_tokens):
    for v in values:
        try:
            args = ("a/b", [v]) if f is _match_any else (v,) if f in (_is_test_path, extract_path_tokens) else ()
            result = f(*args)
            print(f"{f.__name__}({v!r}) -> returned {result!r}")
        except Exception as e:
            print(f"{f.__name__}({v!r}) -> {type(e).__name__}: {e}")
PY

Repository: jaylfc/taOS

Length of output: 2072


Validate nested rule and scan patterns before dispatch.

scripts/check_doc_gate.py only validates outer containers, so valid TOML with scalar or missing nested values such as rules[0].when_changed = 7, rules[0].on_modify = "yes", or referenced_paths_scan = [7] can pass configuration validation and crash during rule matching or the invariant scan. Reject invalid consumed field types before evaluate_rules and check_referenced_paths.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/check_doc_gate.py` around lines 105 - 121, Extend configuration
validation before evaluate_rules and check_referenced_paths to validate each
rule’s consumed fields, including when_changed and on_modify, using the expected
types and rejecting invalid scalar values; also validate every
referenced_paths_scan entry as a string. Keep existing outer-container checks
and raise clear ValueError messages identifying the invalid rule index or scan
entry.

@kilo-code-bot

kilo-code-bot Bot commented Aug 6, 2026

Copy link
Copy Markdown

Code Review Summary

Status: No Issues Found | Recommendation: Merge

Files Reviewed (2 files)
  • scripts/check_doc_gate.py
  • tests/test_doc_gate.py

Reviewed by step-3.7-flash · Input: 57.2K · Output: 9.3K · Cached: 132K

@qodo-code-review

qodo-code-review Bot commented Aug 6, 2026

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (2) 📘 Rule violations (0) 📜 Skill insights (0)

Grey Divider


Remediation recommended

1. Scan entry type unchecked 🐞 Bug ☼ Reliability ⭐ New
Description
_validate_config() only verifies that invariants.referenced_paths_scan is a list, so a TOML list
containing non-strings will pass validation but later crash check_referenced_paths() at `repo_root /
rel` with a TypeError. This produces an unhandled traceback and exit code 1 instead of
EXIT_CONFIG_ERROR.
Code

scripts/check_doc_gate.py[R119-121]

+    scan = invariants.get("referenced_paths_scan", [])
+    if not isinstance(scan, list):
+        raise ValueError("'invariants.referenced_paths_scan' must be a list")
Relevance

●●● Strong

Team often adds defensive shape/type validation to avoid runtime crashes; aligns with PR’s
config-error intent.

PR-#1542

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The new structural validation checks only that referenced_paths_scan is a list, not that its
elements are strings, while check_referenced_paths() later assumes each element can be used as a
Path suffix; non-strings will raise TypeError during path joining and will not be caught by the
config-load try/except.

scripts/check_doc_gate.py[116-121]
scripts/check_doc_gate.py[124-133]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`_validate_config()` validates `invariants.referenced_paths_scan` is a list, but does not validate the element types. If the list contains a non-string TOML value (e.g. an integer), `check_referenced_paths()` later crashes at `repo_root / rel` with `TypeError`, bypassing the new config-error handling and exiting as a generic failure.

### Issue Context
This PR’s intent is to ensure config errors return `EXIT_CONFIG_ERROR` (3) rather than crashing and exiting 1 (violation). A malformed-but-parseable `referenced_paths_scan` list still violates that intent.

### Fix Focus Areas
- scripts/check_doc_gate.py[116-137]
- tests/test_doc_gate.py[299-369]

### Suggested fix
1. In `_validate_config()`, after confirming `scan` is a list, validate `all(isinstance(x, str) for x in scan)` and raise `ValueError` with a clear message (e.g. `"'invariants.referenced_paths_scan' entries must be strings"`) on the first invalid entry.
2. Add a regression test under `TestConfigErrorExitCode` with TOML like:
  ```toml
  [invariants]
  referenced_paths_scan = [123]
  ```
  and assert `dg.main([...]) == dg.EXIT_CONFIG_ERROR` and stderr contains `"config error"`.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Incomplete config type validation 🐞 Bug ≡ Correctness
Description
_validate_config() only checks that 'rules' is a list of tables, but it does not validate per-rule
field types (e.g., when_changed/require_doc must be list[str], on_modify must be bool). Valid TOML
with wrong field types can still raise unhandled exceptions (e.g., TypeError in _match_any) or
silently mis-evaluate rules, causing the tool to exit 1 instead of EXIT_CONFIG_ERROR.
Code

scripts/check_doc_gate.py[R105-108]

+    rules = config.get("rules", [])
+    if not isinstance(rules, list):
+        raise ValueError("'rules' must be a list of tables")
+    for i, rule in enumerate(rules):
Relevance

●●● Strong

Repo has precedent for validating parsed config/data shapes to prevent downstream crashes and
misbehavior.

PR-#1542

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The newly added validator only checks rules is a list and each rule is a dict, but later logic
assumes specific types for rule fields and iterates over them. If a TOML config sets a field like
when_changed to a non-iterable value, _match_any() will raise TypeError, which is not caught
by the config-load try/except (it only wraps load + validation), resulting in an unhandled exception
and exit code 1 instead of EXIT_CONFIG_ERROR.

scripts/check_doc_gate.py[96-122]
scripts/check_doc_gate.py[175-176]
scripts/check_doc_gate.py[222-248]
PR-#1542

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`_validate_config()` currently validates only the container types (e.g. `rules` is a list and each `rule` is a dict), but downstream code assumes specific types for fields inside each rule. Malformed-but-parseable TOML (e.g. `when_changed = 123` or `on_modify = "false"`) can still crash later or behave incorrectly, which defeats the intent of surfacing config problems as `EXIT_CONFIG_ERROR`.

### Issue Context
- `evaluate_rules()` treats `when_changed`/`require_doc` as iterables of glob strings and `on_modify` as a boolean.
- `_match_any()` iterates `patterns`; if a non-iterable sneaks through (e.g. int), it will raise a `TypeError` at runtime.
- Strings are iterable, so wrong types can also silently mis-evaluate rules rather than crashing.

### Fix Focus Areas
- scripts/check_doc_gate.py[96-122]
- scripts/check_doc_gate.py[175-176]
- scripts/check_doc_gate.py[222-248]

### Implementation notes
- Extend `_validate_config()` to validate, for each rule dict:
 - `name` is `str` (optional but if present must be `str`)
 - `when_changed` is `list[str]` (required; or at least if present, must be `list[str]`)
 - `require_doc` is `list[str]` (required; or at least if present, must be `list[str]`)
 - `hint` is `str` (if present)
 - `on_modify` is `bool` (if present)
- Also validate `invariants.referenced_paths_scan` elements are `str` (not just that it is a list).
- Keep raising `ValueError` so the existing `except (..., ValueError)` path continues to return `EXIT_CONFIG_ERROR`.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Context used
✅ Compliance rules (platform): 35 rules

To customize comments, go to the Qodo configuration screen, or learn more in the docs.

Previous review results

Review updated until commit 654a82b ⚖️ Balanced

Results up to commit 654a82b ⚖️ Balanced


🐞 Bugs (1) 📘 Rule violations (0) 📎 Requirement gaps (0) 🎨 UX issues (0) 🔗 Cross-repo conflicts (0) 📜 Skill insights (0)


Remediation recommended
1. Incomplete config type validation 🐞 Bug ≡ Correctness
Description
_validate_config() only checks that 'rules' is a list of tables, but it does not validate per-rule
field types (e.g., when_changed/require_doc must be list[str], on_modify must be bool). Valid TOML
with wrong field types can still raise unhandled exceptions (e.g., TypeError in _match_any) or
silently mis-evaluate rules, causing the tool to exit 1 instead of EXIT_CONFIG_ERROR.
Code

scripts/check_doc_gate.py[R105-108]

+    rules = config.get("rules", [])
+    if not isinstance(rules, list):
+        raise ValueError("'rules' must be a list of tables")
+    for i, rule in enumerate(rules):
Relevance

●●● Strong

Repo has precedent for validating parsed config/data shapes to prevent downstream crashes and
misbehavior.

PR-#1542

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The newly added validator only checks rules is a list and each rule is a dict, but later logic
assumes specific types for rule fields and iterates over them. If a TOML config sets a field like
when_changed to a non-iterable value, _match_any() will raise TypeError, which is not caught
by the config-load try/except (it only wraps load + validation), resulting in an unhandled exception
and exit code 1 instead of EXIT_CONFIG_ERROR.

scripts/check_doc_gate.py[96-122]
scripts/check_doc_gate.py[175-176]
scripts/check_doc_gate.py[222-248]
PR-#1542

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`_validate_config()` currently validates only the container types (e.g. `rules` is a list and each `rule` is a dict), but downstream code assumes specific types for fields inside each rule. Malformed-but-parseable TOML (e.g. `when_changed = 123` or `on_modify = "false"`) can still crash later or behave incorrectly, which defeats the intent of surfacing config problems as `EXIT_CONFIG_ERROR`.

### Issue Context
- `evaluate_rules()` treats `when_changed`/`require_doc` as iterables of glob strings and `on_modify` as a boolean.
- `_match_any()` iterates `patterns`; if a non-iterable sneaks through (e.g. int), it will raise a `TypeError` at runtime.
- Strings are iterable, so wrong types can also silently mis-evaluate rules rather than crashing.

### Fix Focus Areas
- scripts/check_doc_gate.py[96-122]
- scripts/check_doc_gate.py[175-176]
- scripts/check_doc_gate.py[222-248]

### Implementation notes
- Extend `_validate_config()` to validate, for each rule dict:
 - `name` is `str` (optional but if present must be `str`)
 - `when_changed` is `list[str]` (required; or at least if present, must be `list[str]`)
 - `require_doc` is `list[str]` (required; or at least if present, must be `list[str]`)
 - `hint` is `str` (if present)
 - `on_modify` is `bool` (if present)
- Also validate `invariants.referenced_paths_scan` elements are `str` (not just that it is a list).
- Keep raising `ValueError` so the existing `except (..., ValueError)` path continues to return `EXIT_CONFIG_ERROR`.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Qodo Logo

Comment thread scripts/check_doc_gate.py
Comment on lines +105 to +108
rules = config.get("rules", [])
if not isinstance(rules, list):
raise ValueError("'rules' must be a list of tables")
for i, rule in enumerate(rules):

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Remediation recommended

1. Incomplete config type validation 🐞 Bug ≡ Correctness

_validate_config() only checks that 'rules' is a list of tables, but it does not validate per-rule
field types (e.g., when_changed/require_doc must be list[str], on_modify must be bool). Valid TOML
with wrong field types can still raise unhandled exceptions (e.g., TypeError in _match_any) or
silently mis-evaluate rules, causing the tool to exit 1 instead of EXIT_CONFIG_ERROR.
Agent Prompt
### Issue description
`_validate_config()` currently validates only the container types (e.g. `rules` is a list and each `rule` is a dict), but downstream code assumes specific types for fields inside each rule. Malformed-but-parseable TOML (e.g. `when_changed = 123` or `on_modify = "false"`) can still crash later or behave incorrectly, which defeats the intent of surfacing config problems as `EXIT_CONFIG_ERROR`.

### Issue Context
- `evaluate_rules()` treats `when_changed`/`require_doc` as iterables of glob strings and `on_modify` as a boolean.
- `_match_any()` iterates `patterns`; if a non-iterable sneaks through (e.g. int), it will raise a `TypeError` at runtime.
- Strings are iterable, so wrong types can also silently mis-evaluate rules rather than crashing.

### Fix Focus Areas
- scripts/check_doc_gate.py[96-122]
- scripts/check_doc_gate.py[175-176]
- scripts/check_doc_gate.py[222-248]

### Implementation notes
- Extend `_validate_config()` to validate, for each rule dict:
  - `name` is `str` (optional but if present must be `str`)
  - `when_changed` is `list[str]` (required; or at least if present, must be `list[str]`)
  - `require_doc` is `list[str]` (required; or at least if present, must be `list[str]`)
  - `hint` is `str` (if present)
  - `on_modify` is `bool` (if present)
- Also validate `invariants.referenced_paths_scan` elements are `str` (not just that it is a list).
- Keep raising `ValueError` so the existing `except (..., ValueError)` path continues to return `EXIT_CONFIG_ERROR`.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

@jaylfc jaylfc closed this Aug 7, 2026
@jaylfc jaylfc reopened this Aug 7, 2026
Comment thread scripts/check_doc_gate.py
Comment on lines +119 to +121
scan = invariants.get("referenced_paths_scan", [])
if not isinstance(scan, list):
raise ValueError("'invariants.referenced_paths_scan' must be a list")

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Remediation recommended

1. Scan entry type unchecked 🐞 Bug ☼ Reliability

_validate_config() only verifies that invariants.referenced_paths_scan is a list, so a TOML list
containing non-strings will pass validation but later crash check_referenced_paths() at `repo_root /
rel` with a TypeError. This produces an unhandled traceback and exit code 1 instead of
EXIT_CONFIG_ERROR.
Agent Prompt
### Issue description
`_validate_config()` validates `invariants.referenced_paths_scan` is a list, but does not validate the element types. If the list contains a non-string TOML value (e.g. an integer), `check_referenced_paths()` later crashes at `repo_root / rel` with `TypeError`, bypassing the new config-error handling and exiting as a generic failure.

### Issue Context
This PR’s intent is to ensure config errors return `EXIT_CONFIG_ERROR` (3) rather than crashing and exiting 1 (violation). A malformed-but-parseable `referenced_paths_scan` list still violates that intent.

### Fix Focus Areas
- scripts/check_doc_gate.py[116-137]
- tests/test_doc_gate.py[299-369]

### Suggested fix
1. In `_validate_config()`, after confirming `scan` is a list, validate `all(isinstance(x, str) for x in scan)` and raise `ValueError` with a clear message (e.g. `"'invariants.referenced_paths_scan' entries must be strings"`) on the first invalid entry.
2. Add a regression test under `TestConfigErrorExitCode` with TOML like:
   ```toml
   [invariants]
   referenced_paths_scan = [123]
   ```
   and assert `dg.main([...]) == dg.EXIT_CONFIG_ERROR` and stderr contains `"config error"`.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

@qodo-code-review

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit 654a82b

@jaylfc

jaylfc commented Aug 7, 2026

Copy link
Copy Markdown
Owner Author

Review: the fix is correct. The one red check is not yours.

CI has now run properly — this PR had zero workflow runs until 00:05Z because it was opened during the GitHub Actions outage and its pull_request events were dropped; a close/reopen re-emitted them. Worth knowing the green you see is only ~40 minutes old.

Result: 17 success, 1 failure. The single failure is dependency-audit, and it is not caused by this PR.

dev currently runs pip-audit --ignore-vuln CVE-2026-3219 — the pip CVE only. The three cryptography ignores live in #2310, which is under a hold. So dependency-audit fails on every PR against dev until #2310 lands. Nothing in this branch can fix it.

The substance is right, verified rather than skimmed:

  • EXIT_CONFIG_ERROR moved 2 → 3, with 2 explicitly reserved for argparse. That was the collision I raised.
  • _validate_config is actually called (line 315), not merely defined — I checked, because a validator that is never invoked passes inspection while doing nothing.
  • UnicodeDecodeError is caught alongside TOMLDecodeError/OSError/ValueError at the call site, closing the non-UTF-8 hole.
  • All three tests exist, including one asserting the three exit codes are mutually distinguishable and one asserting argparse's usage code differs.
  • STEP 0 was honoured: git rev-list --count origin/exec/tsk-thh54d ^origin/exec/tsk-aerar5 = 0, so this contains all of check_doc_gate.py: a broken/unparseable config exits 1 identically to a real violation #2306 and supersedes it rather than duplicating.

I have not merged it: dependency-audit is a required check and it is red, which my gate refuses on regardless of cause. That is correct behaviour — the queue is blocked by the #2310 decision, not by this work.

When #2310 lands, this should go green unchanged. tsk-thh54d carries the release condition: close #2306 and its card together at that point, or the card becomes claimable and a lane rebuilds work this PR already contains.

@jaylfc jaylfc closed this Aug 9, 2026
@jaylfc jaylfc reopened this Aug 9, 2026
@qodo-code-review

Copy link
Copy Markdown

ⓘ Qodo reviews are paused because the subscription is no longer active. Ask your workspace admin to reactivate the subscription to resume reviews. Manage billing

@jaylfc

jaylfc commented Aug 9, 2026

Copy link
Copy Markdown
Owner Author

recreate-cla

@jaylfc
jaylfc merged commit 16a345b into dev Aug 9, 2026
18 checks passed
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