Skip to content

v0.2.0: gate-hardening — fail-open closes, codex parser currency, idempotent resolve - #2

Merged
OrionArchitekton merged 1 commit into
mainfrom
codex/failclosed-v0.2.0-gate-parser-thread-sync-20260628
Jun 28, 2026
Merged

v0.2.0: gate-hardening — fail-open closes, codex parser currency, idempotent resolve#2
OrionArchitekton merged 1 commit into
mainfrom
codex/failclosed-v0.2.0-gate-parser-thread-sync-20260628

Conversation

@OrionArchitekton

Copy link
Copy Markdown
Owner

failclosed v0.2.0 — gate-hardening release

Ports four in-scope gate/parser/thread fixes from the internal pr-automation
sibling, and — after adversarial review — hardens the thread classifier beyond
its source. Two of the ports closed latent fail-open paths (a merge reported
MERGE_READY while a real, unresolved review signal was still open) — the exact
failure class failclosed exists to prevent.

Changes

# Severity Change
1 BLOCKING (fail-open) bot_authors.yaml: recognize the Codex GitHub reviewer (chatgpt-codex-connector + ^chatgpt-codex). Without it, unresolved Codex threads were dropped before the remaining-threads count → the gate could clear with an open Codex P1 thread.
2 BLOCKING (fail-open) resolve_bot_threads.py: replace the substring informational-classifier with a fail-closed pure-receipt check (NFKC-normalize → carve out matched receipt phrases → any leftover alphanumeric content, in any script, keeps the thread open).
3 WARNING (currency) parse_codex_output.py: parse codex CLI ≥~0.13x native review ([P1] Title — path:start-end): P0–P3 markers + trailing em-dash path:range.
4 INFO (robustness) graphql_helpers.py: is_thread_resolved() + thread_reply_exists(); reply_and_resolve() is now idempotent against double-fire (best-effort probes; never suppresses the resolve).

Plus: version 0.1.00.2.0, CHANGELOG.md.

Why the classifier went beyond a literal port

The ported classifier (pr-automation's "3-gate" version) was found by adversarial
review to still fail open three ways, fixed across three RED→GREEN rounds:

  1. continuation-marker denylist is finite → "No findings since v2 broke" auto-resolved;
  2. word allowlist's ASCII tokenizer was blind to non-Latin / fullwidth-homoglyph
    findings ("No findings. Авторизация сломана", RCE) and admitted its own
    negation ("No findings. Action required.");
  3. final design — NFKC + phrase-span carve-out + Unicode-aware residual — confirmed
    fail-closed by a third adversarial pass. Every verified break is now a regression test.

Note: pr-automation (the internal source) still carries the round-1 residual; a
follow-up should backport this hardened classifier to it.

Verification

  • pytest -q59 passed (was 37 on main).
  • ruff check . → clean.
  • Leak gate → no absolute home paths; gitleaks clean (history + working tree).
  • Adversarial + correctness + security-code reviewers run diff-fed; the one BLOCKING
    they surfaced (classifier fail-open) is fixed and re-confirmed CLOSED.

Deferred (pre-existing in the source; not introduced here)

Tracked, not silently dropped — all WARNING/INFO with low real-world trigger:

  • Parser: _FINDING_MARKER_RE doesn't match indented bullets or P4+; an
    approve verdict co-occurring with a dropped finding could clear the gate
    (requires self-contradictory reviewer output).
  • graphql: reply_and_resolve can double-post a comment if both idempotency
    probes error on a retry; exact-body dedup doesn't match across fix cycles (sha changes).
  • graphql: run_graphql uses gh api graphql -F, which reads a @-prefixed
    value as a file — not reachable today (thread_id is a node ID, body is operator
    text); switch to -f for defence-in-depth.
  • classifier INFO: a body of only a receipt phrase + emoji/invisible chars
    classifies informational; non-exploitable (cannot encode an actionable finding).

No spec file: this is bugfix/port-class work (existing test_*.py seams are the
executable contract), per the repo's spec-persistence rule.

…y, idempotent resolve)

Port four in-scope gate/parser/thread fixes from the internal pr-automation
sibling and harden the thread classifier to be fail-closed by construction:

- bot_authors: recognize the Codex GitHub reviewer (chatgpt-codex-connector /
  ^chatgpt-codex) so unresolved Codex threads are counted, not dropped — closes a
  MERGE_READY fail-open with an open Codex P1 thread.
- resolve_bot_threads: replace the substring informational-classifier with a
  fail-closed pure-receipt check (NFKC-normalize + receipt-phrase span carve-out +
  Unicode-aware residual). Closes three adversarially-verified fail-open vectors
  (continuation-marker denylist bypass, non-Latin/homoglyph tokenizer blind spot,
  vocab-only negation).
- parse_codex_output: parse codex CLI >=~0.13x native review ([P0-P3] markers +
  trailing em-dash path:start-end), alongside existing layouts.
- graphql_helpers: add is_thread_resolved() + thread_reply_exists(); make
  reply_and_resolve() idempotent against double-fire (best-effort probes that never
  suppress the resolve).
- bump version 0.1.0 -> 0.2.0; add CHANGELOG.md.

Tests: 59 passed (was 37). ruff clean. leak gate clean (no home paths, gitleaks).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

@sourcery-ai sourcery-ai 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.

Sorry @OrionArchitekton, you have reached your weekly rate limit of 500000 diff characters.

Please try again later or upgrade to continue using Sourcery

@coderabbitai

coderabbitai Bot commented Jun 28, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@OrionArchitekton, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 5 minutes and 19 seconds. Learn how PR review limits work.

Your organization has used up its prepaid credits, and credit purchases are no longer available. Enable the review add-on in the billing tab to keep reviews running — you're only billed for reviews past your plan's rate limits ($0.25/file).

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based credits.

🚦 How do rate limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 8d089727-4ecb-40fc-bf1e-7a309f6659c2

📥 Commits

Reviewing files that changed from the base of the PR and between 62184ac and d254a89.

📒 Files selected for processing (9)
  • CHANGELOG.md
  • bot_authors.yaml
  • graphql_helpers.py
  • parse_codex_output.py
  • pyproject.toml
  • resolve_bot_threads.py
  • test_graphql_helpers.py
  • test_parse_codex_output.py
  • test_resolve_bot_threads.py
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/failclosed-v0.2.0-gate-parser-thread-sync-20260628

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.

@gemini-code-assist gemini-code-assist 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.

Code Review

This pull request hardens the merge admission control gate to prevent fail-open paths. Key changes include adding support for recognizing Codex reviewer bot authors, implementing idempotent thread reply and resolution in graphql_helpers.py, hardening the informational thread classifier in resolve_bot_threads.py to use a Unicode-aware pure-receipt check, and updating the parser to support newer Codex CLI native review formats. Feedback was provided on graphql_helpers.py to add or {} guards to prevent potential AttributeError exceptions if the GraphQL API returns a null data field.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread graphql_helpers.py
Comment on lines +156 to 173
reply_comment_id = (
reply_result.get("data", {})
.get("addPullRequestReviewThreadReply", {})
.get("comment", {})
.get("id")
),
)
reply_skipped = None

resolve_result = resolve_thread(thread_id)
return {
"thread_id": thread_id,
"reply_comment_id": reply_comment_id,
"resolved": (
resolve_result.get("data", {})
.get("resolveReviewThread", {})
.get("thread", {})
.get("isResolved", False)
),

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

In is_thread_resolved and thread_reply_exists, the robust or {} pattern is used to guard against payload.get("data", {}) returning None (which is a standard GraphQL representation for partial or null data). However, in reply_and_resolve, this guard is missing when accessing reply_result and resolve_result. If the GraphQL API returns a null data field, this will raise an AttributeError: 'NoneType' object has no attribute 'get'. Adding the or {} fallback ensures consistency and prevents potential runtime crashes.

Suggested change
reply_comment_id = (
reply_result.get("data", {})
.get("addPullRequestReviewThreadReply", {})
.get("comment", {})
.get("id")
),
)
reply_skipped = None
resolve_result = resolve_thread(thread_id)
return {
"thread_id": thread_id,
"reply_comment_id": reply_comment_id,
"resolved": (
resolve_result.get("data", {})
.get("resolveReviewThread", {})
.get("thread", {})
.get("isResolved", False)
),
reply_comment_id = (
(reply_result.get("data", {}) or {})
.get("addPullRequestReviewThreadReply", {})
.get("comment", {})
.get("id")
)
reply_skipped = None
resolve_result = resolve_thread(thread_id)
return {
"thread_id": thread_id,
"reply_comment_id": reply_comment_id,
"resolved": (
(resolve_result.get("data", {}) or {})
.get("resolveReviewThread", {})
.get("thread", {})
.get("isResolved", False)
),
"skipped": reply_skipped,
}

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d254a89cfd

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread parse_codex_output.py
_VERDICT_RE = re.compile(r"(?im)^verdict\s*:[ \t]*(.+?)[ \t]*$")
_FINDING_MARKER_RE = re.compile(
r"(?im)^[-*]\s*\[(critical|high|medium|low)\]\s*"
r"(?im)^[-*]\s*\[(critical|high|medium|low|P[0-3])\]\s*"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Accept bare P-level markers

When the native output is the format documented just below as [P1] Title — path:start-end with no Verdict: line, this regex still requires a leading - or *. For that bare-marker output, parse_native_review_stdout extracts zero findings and returns schema_valid=False, so the new Codex format still becomes a parse error instead of producing fix requests.

Useful? React with 👍 / 👎.

@OrionArchitekton
OrionArchitekton merged commit dca135a into main Jun 28, 2026
7 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