Skip to content

check_doc_gate.py: a broken/unparseable config exits 1 identically to a real violation - #2306

Merged
jaylfc merged 1 commit into
devfrom
exec/tsk-thh54d
Aug 9, 2026
Merged

check_doc_gate.py: a broken/unparseable config exits 1 identically to a real violation#2306
jaylfc merged 1 commit into
devfrom
exec/tsk-thh54d

Conversation

@jaylfc

@jaylfc jaylfc commented Aug 5, 2026

Copy link
Copy Markdown
Owner

CARD TITLE (intent, not commit subject): check_doc_gate.py: a broken/unparseable config exits 1 identically to a real violation

Autonomous build of board card tsk-thh54d.

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.

Files:
scripts/check_doc_gate.py | 17 ++++++++++++++---
tests/test_doc_gate.py | 31 +++++++++++++++++++++++++++++++
2 files changed, 45 insertions(+), 3 deletions(-)

Summary by CodeRabbit

  • Bug Fixes

    • Configuration files that are missing or invalid now produce a distinct error status and a clear error message.
    • Genuine documentation-rule violations continue to return the appropriate violation status.
  • Tests

    • Added coverage to verify separate statuses for configuration errors and documentation violations.

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.
@coderabbitai

coderabbitai Bot commented Aug 5, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The documentation gate now uses named exit codes for success, rule violations, and configuration errors. Configuration-loading failures are reported to stderr and return the configuration-error code. Tests cover malformed and missing configuration files and preserve the violation code for rule failures.

Changes

Documentation gate exit status handling

Layer / File(s) Summary
Exit code contract and reporting
scripts/check_doc_gate.py
The script defines distinct exit-code constants and uses named success and violation codes in _report.
Configuration error handling and validation
scripts/check_doc_gate.py, tests/test_doc_gate.py
Configuration parsing and file-access failures return EXIT_CONFIG_ERROR with stderr output. Tests verify these failures and confirm rule violations return EXIT_VIOLATION.

Estimated code review effort: 2 (Simple) | ~10 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 16.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly identifies the configuration exit-code problem addressed by the pull request.
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.
✨ Finishing Touches 💡 1
📝 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-thh54d

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 5, 2026

Copy link
Copy Markdown

Important

You are using the Gitar free plan. Upgrade to unlock code review, CI analysis, auto-apply, custom automations, and more.

Gitar

@qodo-code-review

Copy link
Copy Markdown

PR Summary by Qodo

Doc-gate: exit 2 on config errors (distinct from violations)

🐞 Bug fix 🧪 Tests 🕐 20-40 Minutes

Grey Divider

AI Description

• Return exit code 2 for missing/unparseable doc-gate config.
• Print a clear config error to stderr instead of an unhandled traceback.
• Add regression tests for config errors vs real doc-gate violations.
Diagram

graph TD
  A["check_doc_gate CLI (main)"] --> B["load_config()"]
  B --> C["Config error -> stderr"] --> D(("Exit 2"))
  B --> E["Run invariants/diff-gate"] --> F["_report()"] --> G(("Exit 0/1"))
Loading
High-Level Assessment

The chosen approach (catch TOMLDecodeError/OSError in main(), print stderr message, and return a distinct exit code) is the most direct way to make CI failures actionable without changing the rest of the rule engine. Alternatives like raising a custom exception or moving error handling into load_config() add indirection without meaningful benefit here.

Files changed (2) +45 / -3

Bug fix (1) +14 / -3
check_doc_gate.pyIntroduce distinct exit codes and handle config parse/missing errors +14/-3

Introduce distinct exit codes and handle config parse/missing errors

• Adds named exit-code constants and updates reporting to use them. Wraps config loading to catch TOML parse errors and missing/unreadable config files, printing a clear stderr message and returning exit code 2 so config issues are distinguishable from real doc-gate violations.

scripts/check_doc_gate.py

Tests (1) +31 / -0
test_doc_gate.pyAdd regression tests for config-error exit code behavior +31/-0

Add regression tests for config-error exit code behavior

• Introduces a dedicated test class verifying that unparseable and missing TOML configs return EXIT_CONFIG_ERROR (2) and that real rule violations continue to return EXIT_VIOLATION (1). Ensures stderr messaging is emitted for parse errors.

tests/test_doc_gate.py

@jaylfc

jaylfc commented Aug 5, 2026

Copy link
Copy Markdown
Owner Author

nemotron-super review

VERDICT: Pass
No blocking issues found.

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

@kilo-code-bot

kilo-code-bot Bot commented Aug 5, 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: 62K · Output: 6.4K · Cached: 244.9K

@qodo-code-review

Copy link
Copy Markdown

Code Review by Qodo

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

Grey Divider


Remediation recommended

1. Schema errors still crash 🐞 Bug ☼ Reliability
Description
main() only maps TOML parse/read failures to EXIT_CONFIG_ERROR; a syntactically valid but
structurally invalid config (wrong types for keys like gate/rules) can still raise later and likely
exit 1, making some broken configs indistinguishable from real violations again.
Code

scripts/check_doc_gate.py[R283-286]

+    try:
+        config = load_config(args.config)
+    except (tomllib.TOMLDecodeError, OSError) as e:
+        print(f"doc-gate: config error: {args.config}: {e}", file=sys.stderr)
Relevance

●●● Strong

Team often accepts script hardening to distinguish real failures from misconfig/crashes; matches PR
intent.

PR-#1551
PR-#1542
PR-#398

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The new try/except only handles parse/read errors around load_config(), but the script later
assumes config subtrees are dict-like; if they’re the wrong type, it will raise after parsing and
won’t return EXIT_CONFIG_ERROR.

scripts/check_doc_gate.py[270-287]
scripts/check_doc_gate.py[255-258]

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

## Issue description
`scripts/check_doc_gate.py` now returns `EXIT_CONFIG_ERROR` for `TOMLDecodeError` and `OSError` during `load_config()`, but it still assumes the loaded TOML has the expected structure/types. A syntactically valid TOML with wrong types (e.g., `gate = "x"`) can crash later (e.g., `str` has no `.get()`), which typically exits with status 1 and undermines the “config errors are distinct from violations” contract.

## Issue Context
The PR’s goal is to ensure config problems do not look like doc-gate violations. This currently holds for unreadable/unparseable files, but not for schema/type errors.

## Fix Focus Areas
- scripts/check_doc_gate.py[255-258]
- scripts/check_doc_gate.py[270-307]

## Suggested implementation sketch
- Add a small `validate_config(config: dict) -> None` that checks:
 - `config` is a `dict`
 - `gate` is absent or a `dict`
 - `rules` is absent or a `list` of `dict`
 - any other sections used in `main()` (`invariants.referenced_paths_scan`, etc.) have expected types
- Call `validate_config()` immediately after `load_config()`.
- Catch `ValueError` (raised by validation) in the same `except` block and return `EXIT_CONFIG_ERROR`.
- Add a unit test with a syntactically valid TOML that violates schema (e.g., `gate = "x"`) asserting `EXIT_CONFIG_ERROR` and a clear stderr message.

ⓘ 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.

Qodo Logo

Comment thread scripts/check_doc_gate.py
Comment on lines +283 to +286
try:
config = load_config(args.config)
except (tomllib.TOMLDecodeError, OSError) as e:
print(f"doc-gate: config error: {args.config}: {e}", file=sys.stderr)

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. Schema errors still crash 🐞 Bug ☼ Reliability

main() only maps TOML parse/read failures to EXIT_CONFIG_ERROR; a syntactically valid but
structurally invalid config (wrong types for keys like gate/rules) can still raise later and likely
exit 1, making some broken configs indistinguishable from real violations again.
Agent Prompt
## Issue description
`scripts/check_doc_gate.py` now returns `EXIT_CONFIG_ERROR` for `TOMLDecodeError` and `OSError` during `load_config()`, but it still assumes the loaded TOML has the expected structure/types. A syntactically valid TOML with wrong types (e.g., `gate = "x"`) can crash later (e.g., `str` has no `.get()`), which typically exits with status 1 and undermines the “config errors are distinct from violations” contract.

## Issue Context
The PR’s goal is to ensure config problems do not look like doc-gate violations. This currently holds for unreadable/unparseable files, but not for schema/type errors.

## Fix Focus Areas
- scripts/check_doc_gate.py[255-258]
- scripts/check_doc_gate.py[270-307]

## Suggested implementation sketch
- Add a small `validate_config(config: dict) -> None` that checks:
  - `config` is a `dict`
  - `gate` is absent or a `dict`
  - `rules` is absent or a `list` of `dict`
  - any other sections used in `main()` (`invariants.referenced_paths_scan`, etc.) have expected types
- Call `validate_config()` immediately after `load_config()`.
- Catch `ValueError` (raised by validation) in the same `except` block and return `EXIT_CONFIG_ERROR`.
- Add a unit test with a syntactically valid TOML that violates schema (e.g., `gate = "x"`) asserting `EXIT_CONFIG_ERROR` and a clear stderr message.

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

@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 283-287: Update the exception tuple in the config-loading handler
around load_config to include UnicodeDecodeError, ensuring invalid UTF-8 returns
EXIT_CONFIG_ERROR with the existing diagnostic. Add a regression test that
writes invalid bytes such as b"\xff" and verifies the configuration-error
outcome.
🪄 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: f8a92547-54d4-468c-af3e-a9e398ea0df7

📥 Commits

Reviewing files that changed from the base of the PR and between 07610a3 and 94e8436.

📒 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 +283 to +287
try:
config = load_config(args.config)
except (tomllib.TOMLDecodeError, OSError) as e:
print(f"doc-gate: config error: {args.config}: {e}", file=sys.stderr)
return EXIT_CONFIG_ERROR

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail
python - <<'PY'
import io
import tomllib

try:
    tomllib.load(io.BytesIO(b"\xff"))
except UnicodeDecodeError:
    print("confirmed")
else:
    raise SystemExit("expected UnicodeDecodeError")
PY

Repository: jaylfc/taOS

Length of output: 159


🏁 Script executed:

#!/bin/bash
set -euo pipefail

python - <<'PY'
import io, tomllib

for value in (b"\xff", b"\x80", b"\x80\x80\x80"):
    try:
        tomllib.load(io.BytesIO(value))
    except Exception as exc:
        print(type(exc).__name__, bool(isinstance(exc, tomllib.TOMLDecodeError)), bool(isinstance(exc, UnicodeDecodeError)))
    else:
        print("parsed OK")
PY

printf '\n--- scripts/check_doc_gate.py context ---\n'
sed -n '250,300p' scripts/check_doc_gate.py

printf '\n--- config error tests/usages ---\n'
rg -n "EXIT_CONFIG_ERROR|load_config|TOMLDecodeError|UnicodeDecodeError|bad\.write_bytes|config error" scripts -S

Repository: jaylfc/taOS

Length of output: 2804


Handle invalid UTF-8 as a configuration error.

tomllib.load() decodes the file before TOML parsing, so invalid UTF-8 raises UnicodeDecodeError, not tomllib.TOMLDecodeError. Catch that exception here so a corrupt configuration returns EXIT_CONFIG_ERROR instead of escaping the config-error handler. Add a regression test that writes invalid UTF-8 bytes, such as bad.write_bytes(b"\xff").

Proposed fix
-    except (tomllib.TOMLDecodeError, OSError) as e:
+    except (tomllib.TOMLDecodeError, UnicodeDecodeError, OSError) as e:
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
try:
config = load_config(args.config)
except (tomllib.TOMLDecodeError, OSError) as e:
print(f"doc-gate: config error: {args.config}: {e}", file=sys.stderr)
return EXIT_CONFIG_ERROR
try:
config = load_config(args.config)
except (tomllib.TOMLDecodeError, UnicodeDecodeError, OSError) as e:
print(f"doc-gate: config error: {args.config}: {e}", file=sys.stderr)
return EXIT_CONFIG_ERROR
🤖 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 283 - 287, Update the exception tuple
in the config-loading handler around load_config to include UnicodeDecodeError,
ensuring invalid UTF-8 returns EXIT_CONFIG_ERROR with the existing diagnostic.
Add a regression test that writes invalid bytes such as b"\xff" and verifies the
configuration-error outcome.

@jaylfc

jaylfc commented Aug 6, 2026

Copy link
Copy Markdown
Owner Author

Reviewed against tsk-thh54d's red-first list. The core works and I proved it both ways rather than reading it. One defect blocks merge, and it is the same class the card exists to eliminate.

The feature is real, verified red-then-green. On this branch:

--config /tmp/nope.toml diff-gate --staged
  -> doc-gate: config error: /tmp/nope.toml: [Errno 2] No such file or directory   exit=2
--config /tmp/bad.toml  diff-gate --staged
  -> doc-gate: config error: /tmp/bad.toml: Expected '=' after a key in a key/value pair (at line 1, column 6)   exit=2
genuine violation -> exit=1        clean -> exit=0

On dev, both config cases produce a raw FileNotFoundError / tomllib.TOMLDecodeError traceback and exit 1, indistinguishable from a rule violation. So the card's premise is confirmed and the fix addresses it. Messages name the path and, for the parse error, the line and column. load_config opens and parses directly, so OSError and TOMLDecodeError genuinely propagate and the except clause is live rather than inert. Exit 9 is correctly avoided.


BLOCKING — EXIT_CONFIG_ERROR = 2 collides with argparse's own exit code

argparse exits 2 on any usage error. So after this change:

check_doc_gate.py diff-gate --nonsense-flag
  -> check_doc_gate.py diff-gate: error: one of the arguments --staged --base is required
  -> exit=2          # identical to a broken config

"The config is missing or unparseable" and "you invoked the command wrong" now return the same code. The card's stated goal is that these stop looking alike; this collapses two of them again, one step to the left.

This is not hypothetical and it nearly fooled me. My first test run put --config after the subcommand, argparse rejected it as an unrecognised argument, and I got exit=2 on all three cases — which looked exactly like the feature working. It was argparse, and the config path never executed. I only caught it because the printed message was a usage block rather than the doc-gate: config error: line. A caller branching on the exit code has no such tell.

That matters most in CI, which is where this code is designed to be read by a machine: a workflow that grows a typo in its check_doc_gate.py invocation will report "config error" and send whoever is on call to inspect a doc-gate.toml that is perfectly fine.

Fix: use an exit code argparse does not claim. EXIT_CONFIG_ERROR = 3 is the obvious choice (2 is argparse, 1 is a violation, 9 belongs to gate_merge.sh for stale-check aborts). Please also add a test asserting a malformed command line and a broken config return different codes — that is the property the card is actually buying, and nothing currently pins it.

Smaller

The exit-code constants are a genuine improvement and worth carrying into gate_merge.sh's expectations, since that script now has to distinguish 1 / 3 / 9. Worth confirming in this PR whether anything already branches on check_doc_gate.py's exit code, so the new code does not surprise an existing caller.


Once the code moves off 2 and the "different codes" test lands, this is good to merge. Related work, not for this branch: I have carded three separate rule-engine defects found in the same file (tsk-vs6c77 deletion satisfies a rule, tsk-ymv3bb rename bypass, tsk-xe2tt5 git failures indistinguishable from violations). tsk-xe2tt5 is deliberately blocked on this PR so it can reuse whatever code lands here — please leave it alone in this branch so this one stays reviewable against its own list.

@jaylfc

jaylfc commented Aug 6, 2026

Copy link
Copy Markdown
Owner Author

Correcting my own verdict above. I closed that review with "once the code moves off 2 and the 'different codes' test lands, this is good to merge." That was premature. Qodo and CodeRabbit each filed a finding I had missed, both are correct, and I verified both on this branch rather than taking their word.

Between them they mean the config-error path still has two holes of exactly the class this card exists to close.

1. A structurally invalid config still exits 1 (qodo). Valid TOML, wrong shape:

# /tmp/struct.toml
rules = "not a list"
[gate]
trailer = "X:"
--config /tmp/struct.toml diff-gate --staged
  AttributeError: 'str' object has no attribute 'get'
  exit=1

load_config succeeds because the TOML parses; the failure happens later, in the rule loop. So a broken config is once again indistinguishable from a documentation violation, which is the precise thing the card asks to eliminate.

2. Invalid UTF-8 in the config file still exits 1 (CodeRabbit). tomllib.load decodes before parsing, so a bad byte raises UnicodeDecodeError:

--config /tmp/badbytes.toml diff-gate --staged
  UnicodeDecodeError: 'utf-8' codec can't decode byte 0xff in position 0
  exit=1

And the except clause cannot catch it:

issubclass(UnicodeDecodeError, OSError)                -> False
issubclass(UnicodeDecodeError, tomllib.TOMLDecodeError) -> False

UnicodeDecodeError is a ValueError. A truncated download, a file saved in the wrong encoding, or a mangled merge all land here.

Revised asks, replacing the single one in my earlier comment:

  1. Move off exit code 2 (argparse owns it) — as before, exit 3.
  2. Add UnicodeDecodeError (or ValueError) to the caught set around load_config.
  3. Validate the config's shape after parsing, not just that it parsed: rules is a list of tables, gate is a table, each rule's when_changed / require_doc are lists. Report shape failures through the same config-error exit code, naming the offending key.
  4. Tests for all three, each proven red on the current commit first: structurally invalid config, non-UTF-8 config, and malformed command line vs broken config returning different codes.

Point 3 is the one worth doing properly rather than minimally. "The config file loaded" and "the config file is usable" are different claims, and this PR currently only checks the first — which is the same absent-versus-negative distinction that keeps producing these bugs.

Credit to both bots; my review would have let this through with two of the three holes still open. Noting for the record that CodeRabbit's finding here came with an executed script demonstrating the decode failure, not just an assertion, which is why it was quick to confirm.

@jaylfc jaylfc added the lead-blocked Lead has blocked this PR; gate_merge.sh refuses at exit 10. label Aug 6, 2026
@jaylfc
jaylfc merged commit 1e73054 into dev Aug 9, 2026
20 of 21 checks passed
@jaylfc

jaylfc commented Aug 9, 2026

Copy link
Copy Markdown
Owner Author

Closing as superseded by #2312 (its fix-forward), which merged with the corrected config-error exit paths plus the review fixes, STEP-0 verified (no commits lost). The doc-gate work continues on dev.

hognek pushed a commit to hognek/tinyagentos that referenced this pull request Aug 13, 2026
…figs

Pr jaylfc#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 jaylfc#2306).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

lead-blocked Lead has blocked this PR; gate_merge.sh refuses at exit 10.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant