Skip to content

fix(ci): eliminate dev-release tag-vs-downstream race + CI hygiene audit#1827

Merged
Aureliolo merged 4 commits into
mainfrom
fix/dev-release-tag-race
May 8, 2026
Merged

fix(ci): eliminate dev-release tag-vs-downstream race + CI hygiene audit#1827
Aureliolo merged 4 commits into
mainfrom
fix/dev-release-tag-race

Conversation

@Aureliolo
Copy link
Copy Markdown
Owner

closes #1818

Summary

Fixes the tag-vs-downstream-checkout race in dev-release.yml (#1818) and bundles five MED-severity CI hygiene findings the audit surfaced into the same PR.

What changed

Primary fix (#1818)

  • dev-release.yml: dropped the gh api -X DELETE git/refs/tags/$DEV_TAG cleanup that fired on gh release create failure. The tag-create push had already triggered downstream tags: v*-listening workflows (cli.yml, docker.yml); deleting the tag while their actions/checkout step was in flight is what produced the fatal: couldn't find remote ref refs/tags/v0.8.2-dev.2 404 reported in the issue. A failed release-create now exits 1 with the orphan tag preserved; the existing 5-most-recent dev-pre-release sweeper + finalize-release.yml's stable-release sweep garbage-collect it later.
  • dev-release.yml: new end-of-job step Verify minted tag survived the run re-resolves refs/tags/$DEV_TAG and exits 1 with ::error:: if absent, routing through the existing report-failure job into the dev-release regression tracking issue. Catches any future regression directly instead of debugging from downstream symptoms.
  • scripts/check_workflow_tag_lifecycle.py (new): static pre-push gate enforcing "no tag CREATE + conditional DELETE within a single workflow file". Modelled on scripts/check_workflow_shell_git_commits.py. Wired in .pre-commit-config.yaml as id: workflow-tag-lifecycle. Per the CLAUDE.md "Convention Rollout (MANDATORY)" rule: every new convention ships its enforcement gate.

Audit-bundled fixes

  • finalize-release.yml: cleanup loops (dev-release sweep + orphan-tag sweep) replaced \|\| true-suppressed while read shape with explicit-capture-and-check + mapfile + per-tag warnings + final exit-on-failure. Pre-PR review surfaced that mapfile -t arr < <(gh api ...) does not propagate the inner gh api exit code; the second-round fix captures the API output to a variable with explicit exit check before mapfile reads from it. Either failure mode (API down, partial deletes) now fails the job loudly.
  • finalize-release.yml: dropped 2>/dev/null on gh api commits/$HEAD_SHA/pulls (Highlights PR lookup) so auth / rate-limit / API errors surface in stderr; kept on three legitimate not-found-fallback sites with intent comments. Pre-PR review surfaced that the surrounding gh pr view ... \|\| true was conflating "PR genuinely deleted" (legit skip) with "auth failure"; that path now splits into capture + classify, with ::notice:: on not-found and ::warning:: on real errors.
  • cli.yml: split the fuzz step into List fuzz targets (no continue-on-error, fails loudly on go test -list compile errors) + Run fuzz tests (continue-on-error retained, fuzzer-found crashes remain advisory via the existing Report fuzz outcome warning). Compile failures in fuzz targets were previously invisible.
  • ci.yml: branch-protection-drift audit kept its job-level continue-on-error: true (intentional during the refactor(release): GitHub App + full release-pipeline rework (signed commits end-to-end, consolidated workflows, safety nets) #1555 stabilization window), but a new always-running Report drift outcome step now emits ::warning:: annotations whenever the audit detected drift. Real drift is visible in run summaries even while the job stays green.
  • .pre-commit-config.yaml: wired actionlint v1.7.12 and zizmor v1.24.1 as workflow-scoped pre-commit hooks (files: ^\.github/workflows/.*\.ya?ml$). actionlint is NOT in the pre-commit.ci skip: list (no dedicated CI job covers it); zizmor is added to the skip list (already covered by zizmor.yml).

Docs

  • CLAUDE.md: appended check_workflow_tag_lifecycle.py to the existing gate inventory list under "Convention Rollout (MANDATORY)".
  • docs/reference/claude-reference.md: updated "Dev Release" + "Finalize Release" subsections to reflect orphan-tag preservation, the regression-guard step, the mapfile-based cleanup pattern, and the gh-pr-view capture+classify split. Pre-PR review surfaced this as MAJOR doc drift.

Audit confirmation

  • The only workflows listening on tags: v* push events are cli.yml:9-10 and docker.yml:6 (verified via Grep over .github/workflows/). finalize-release.yml uses workflow_run (not tags: v*), so it is not directly affected by the race.
  • Post-fix, no workflow file contains both a gh api ... /git/refs POST and a gh api -X DELETE git/refs/tags/... (verified by running scripts/check_workflow_tag_lifecycle.py --scan-all; exit 0).
  • The gate was tested against a deliberate-violation tempfile and correctly exited 1 with both CREATE and DELETE sites reported with line numbers.

Test plan

Workflow YAML changes have a small static-test surface; validation done via:

  • actionlint v1.7.12 — clean on all four changed workflows.
  • zizmor v1.24.1 — clean on all four changed workflows.
  • pre-commit run --all-files — all 60+ hooks pass (including the new workflow-tag-lifecycle gate).
  • The new gate manually verified against a deliberate-violation tempfile: correctly fires with exit 1, reports CREATE at file:8 + DELETE at file:12.
  • Post-merge live verification: the first dev-release run on main after merge will exercise both the Verify minted tag survived the run regression guard and the hardened finalize-release.yml cleanup paths.

Review coverage

Pre-PR review ran 8 specialized agents:

  • 6 reported clean: comment-quality-rot, comment-analyzer, security-reviewer, infra-reviewer, issue-resolution-verifier, tool-parity-checker.
  • 4 valid findings addressed in commit 2 (a4ce64b99):
    1. mapfile -t arr < <(gh api ...) silently swallowed inner-process failures (CRITICAL — same silent-failure class as ci(dev-release): tag created before release object causes downstream CLI/Docker checkout 404 when release-create fails #1818).
    2. Same pattern on the orphan-tag loop (MAJOR).
    3. Doc drift in docs/reference/claude-reference.md (MAJOR).
    4. gh pr view ... \|\| true conflating "not found" with "auth failure" (MEDIUM).
  • 1 false positive: docs-consistency claimed CLAUDE.md gate-inventory alphabetical order was broken; the recommended order matched the file's actual order. No action.

Issue #1818 acceptance criteria all RESOLVED (verified by issue-resolution-verifier at 100% confidence on each).

Acceptance criteria mapping

  • AC1 — Dev Release no longer races CLI/Docker on tag deletion (drop cleanup delete + regression guard, evidence dev-release.yml:189-209 + 230-252).
  • AC2 — Audit confirms no other workflow has create-then-conditionally-delete pattern (finalize-release.yml only deletes; cli.yml/docker.yml only consume tags) + future enforcement via scripts/check_workflow_tag_lifecycle.py.
  • AC3 — Smoke test that catches regression on next occurrence (the Verify minted tag survived the run step, routed through existing report-failure tracking-issue lane).

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 8, 2026

Dependency Review

✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.

Scanned Files

None

@Aureliolo Aureliolo temporarily deployed to cloudflare-preview May 8, 2026 22:16 — with GitHub Actions Inactive
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 8, 2026

Review Change Stack
No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 3d5bad7a-0b74-42de-9754-d425709710f5

📥 Commits

Reviewing files that changed from the base of the PR and between 6cdfce5 and 05c40aa.

📒 Files selected for processing (4)
  • .github/workflows/finalize-release.yml
  • .pre-commit-config.yaml
  • scripts/check_workflow_tag_lifecycle.py
  • tests/unit/scripts/test_check_workflow_tag_lifecycle.py
📜 Recent review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: Lighthouse Site
  • GitHub Check: Build Web Assets (melange)
  • GitHub Check: Dashboard Test
  • GitHub Check: Test (Python 3.14)
  • GitHub Check: Analyze (python)
🧰 Additional context used
📓 Path-based instructions (4)
**/*.{py,pyi}

📄 CodeRabbit inference engine (CLAUDE.md)

Use Python 3.14+ with PEP 649 native lazy annotations; never use from __future__ import annotations

Use PEP 758 exception syntax: except A, B: without parens when not binding; as exc requires parens

All public functions must have type hints; mypy strict type checking is required

Docstrings must use Google style on public classes and functions (ruff D rules enforced)

Files:

  • scripts/check_workflow_tag_lifecycle.py
  • tests/unit/scripts/test_check_workflow_tag_lifecycle.py
.github/workflows/**/*.{yml,yaml}

📄 CodeRabbit inference engine (CLAUDE.md)

D2 CLI is pinned to v0.7.1 in .github/workflows/pages.yml; D2 diagrams use theme 200 (Dark Mauve), dark-only, configured in mkdocs.yml

Files:

  • .github/workflows/finalize-release.yml
tests/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

Every Mock()/AsyncMock()/MagicMock() in tests/ MUST declare spec=ConcreteClass; pre-existing sites frozen in scripts/mock_spec_baseline.txt; without spec= mocks silently absorb every attribute access

For time-driven tests: import FakeClock from tests._shared.fake_clock; inject via clock= parameter; FakeClock.sleep advances virtual time and yields once via asyncio.sleep(0); patch time.monotonic()/asyncio.sleep() globals only for legacy paths without a Clock seam

Use pytest markers @pytest.mark.unit/@pytest.mark.integration/@pytest.mark.e2e/@pytest.mark.slow for test categorization

Test coverage must be 80% minimum (CI); benchmarks are excluded; run via pytest with --cov=synthorg --cov-fail-under=80

Always run pytest with -n 8 --dist=loadfile for parallelism; never use -n 0 or --dist=worksteal on Python 3.14 due to cumulative resource leaks with Windows ProactorEventLoop

Test flakiness must be fixed fundamentally, never skipped/xfailed/dismissed; use FakeClock-first when the class accepts clock=; for 'block until cancelled', use asyncio.Event().wait() not asyncio.sleep(large)

Files:

  • tests/unit/scripts/test_check_workflow_tag_lifecycle.py

⚙️ CodeRabbit configuration file

Test files do not require Google-style docstrings on classes or functions -- ruff D rules are only enforced on src/. A bare @settings() decorator with no arguments on Hypothesis property tests is a no-op and should not be suggested -- the HYPOTHESIS_PROFILE env var controls example counts via registered profiles, which @given() honors automatically.

Files:

  • tests/unit/scripts/test_check_workflow_tag_lifecycle.py
tests/unit/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

Unit tests run under WindowsSelectorEventLoopPolicy (set by tests/unit/conftest.py) to avoid Python 3.14 IOCP teardown race (CPython #116773) that crashes xdist workers; tool tests that drive real asyncio.create_subprocess_exec override back to the default policy in tests/unit/tools/conftest.py

Files:

  • tests/unit/scripts/test_check_workflow_tag_lifecycle.py
🧠 Learnings (2)
📓 Common learnings
Learnt from: CR
Repo: Aureliolo/synthorg

Timestamp: 2026-05-08T23:24:13.654Z
Learning: Every implementation plan must be presented to the user for accept/deny before coding; be critical at every phase; surface improvements as suggestions, not silent changes; prioritize by dependency order, not priority labels
Learnt from: CR
Repo: Aureliolo/synthorg

Timestamp: 2026-05-08T23:24:13.654Z
Learning: Always read the relevant docs/design/ page before implementing or planning; deviations require explicit user approval; update the design page when an approved deviation lands; never silently diverge
Learnt from: CR
Repo: Aureliolo/synthorg

Timestamp: 2026-05-08T23:24:13.654Z
Learning: After finishing an issue: branch (<type>/<slug>), commit, push; do NOT auto-create a PR; ALWAYS use /pre-pr-review to create PRs; after the PR exists, /aurelio-review-pr handles external reviewer feedback; fix EVERYTHING valid review agents find, including pre-existing issues, suggestions, and findings adjacent to PR changes
Learnt from: CR
Repo: Aureliolo/synthorg

Timestamp: 2026-05-08T23:24:13.654Z
Learning: When tests fail due to timeout/slowness/xdist contention: NEVER delete, skip, or xfail; NEVER --no-verify; NEVER edit tests/baselines/unit_timing.json; fix the source code, not the tests; suite time exceeding baseline * 1.3 indicates a source-code regression
Learnt from: CR
Repo: Aureliolo/synthorg

Timestamp: 2026-05-08T23:24:13.654Z
Learning: Never use cd in Bash commands; cwd is already project root; exception: bash -c 'cd <dir> && <cmd>' (child process); never use Bash to write files (cat >, cat << EOF, echo >, echo >>, sed -i, python -c open().write(), tee); use Write or Edit tool instead
📚 Learning: 2026-05-05T09:04:46.195Z
Learnt from: Aureliolo
Repo: Aureliolo/synthorg PR: 1760
File: scripts/_dual_backend_parity_lib.py:215-216
Timestamp: 2026-05-05T09:04:46.195Z
Learning: This repository targets Python 3.14+ and follows PEP 758. Therefore, reviewer tooling should NOT treat unparenthesized multi-exception `except` clauses written without an `as` clause (e.g., `except MemoryError, RecursionError:`) as syntax errors. Only flag `except`-clause problems when they are genuinely invalid for Python 3.14+.

Applied to files:

  • scripts/check_workflow_tag_lifecycle.py
  • tests/unit/scripts/test_check_workflow_tag_lifecycle.py
🔇 Additional comments (7)
scripts/check_workflow_tag_lifecycle.py (1)

1-276: LGTM!

The gate script is well-structured with comprehensive regex patterns that address the previous review concerns:

  • Both -f and -F field flags are matched (lines 69-72, 86-88)
  • Backslash-newline continuations are tolerated in both CREATE and DELETE patterns
  • The opt-out mechanism with mandatory justification is properly implemented
  • Type hints are present on all public functions per coding guidelines
.github/workflows/finalize-release.yml (3)

149-198: LGTM!

The bounded polling implementation properly addresses the previous review concern about transient 404s. The 6-attempt retry with backoff (totaling ~105s) gives the API adequate time to surface newly created releases, while auth/rate-limit errors fail fast with clear diagnostics.


420-453: LGTM!

The PR view capture/classify split correctly distinguishes between:

  • PR genuinely deleted (404 → ::notice::, skip propagation)
  • Auth/rate-limit/API errors (→ ::warning:: with stderr, skip propagation)

This preserves best-effort semantics while making failures observable.


690-742: LGTM!

The dev cleanup refactor properly addresses the mapfile exit-code propagation issue. Capturing gh api output to a variable with explicit || { exit 1 } before feeding mapfile ensures API failures are loud. The per-tag failure tracking arrays and final aggregated exit code provide good observability for partial-success cleanups.

tests/unit/scripts/test_check_workflow_tag_lifecycle.py (1)

1-358: LGTM!

Comprehensive test coverage for the workflow tag lifecycle checker:

  • Positive and negative cases for both CREATE and DELETE patterns
  • Multi-line continuation handling
  • Typed field -F flag variants (addressing prior review feedback)
  • Comment scrubbing with line number preservation regression test
  • Opt-out marker validation including mandatory justification
  • Repository smoke test ensuring shipped workflows comply

The pytestmark = pytest.mark.unit annotation satisfies the test categorization guideline.

.pre-commit-config.yaml (2)

226-239: LGTM!

The workflow-tag-lifecycle hook correctly addresses both prior review concerns:

  1. The files: pattern includes scripts/check_workflow_tag_lifecycle\.py and \.pre-commit-config\.yaml, making the hook self-protecting against weakening edits
  2. pass_filenames: false with --scan-all ensures a full workflow scan regardless of which trigger file changed

145-156: LGTM!

The new actionlint and zizmor hooks are properly configured:

  • Both pinned to specific versions (v1.7.12, v1.24.1)
  • Scoped to relevant workflow files
  • zizmor correctly includes .github/.zizmor.yml in its files pattern and passes the config flag
  • zizmor is skipped on pre-commit.ci (covered by dedicated CI job), while actionlint runs there as the only enforcement layer for contributors who skip local hooks

Walkthrough

This PR hardens GitHub release automation workflows to prevent the tag-vs-downstream-checkout race documented in #1818. The primary fix preserves dev tags on gh release create failure instead of deleting them, preventing downstream CLI and Docker workflows from encountering 404 errors. A new end-of-run verification step confirms the tag survived, and a new Python gate script (check_workflow_tag_lifecycle.py) scans workflows to prevent reintroduction of the problematic create-then-delete pattern. The finalize-release workflow's preflight checks and dev cleanup logic are refactored for better error observability and failure isolation. Two new pre-commit hooks (actionlint and zizmor) are configured to validate GitHub Actions workflows. Additionally, the CLI fuzz testing is split into discovery and execution phases, and branch protection audit reporting is enhanced with explicit step IDs and warning annotations.

Suggested labels

autorelease: tagged

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main changes: fixing the dev-release tag-vs-downstream race (primary issue #1818) and bundling CI hygiene audit findings, which matches the core objectives.
Description check ✅ Passed The description is comprehensive and directly related to the changeset, covering the primary fix (#1818), audit-bundled fixes, and documentation updates with clear justification for each change.
Linked Issues check ✅ Passed The PR meets all three acceptance criteria from #1818: (1) eliminated tag-deletion race by removing gh api DELETE and adding Verify step; (2) audited all tag-listening workflows and added enforcement gate; (3) implemented regression smoke test via end-of-job verification step routed through report-failure tracking.
Out of Scope Changes check ✅ Passed All changes are directly scoped to #1818 objectives and bundled CI hygiene audit findings. No unrelated modifications detected; even auxiliary improvements (actionlint/zizmor wiring, documentation updates) serve to support the primary race-condition fix and prevent regressions.
Docstring Coverage ✅ Passed Docstring coverage is 70.37% which is sufficient. The required threshold is 40.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


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

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

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 introduces a new pre-commit gate, workflow-tag-lifecycle, implemented via a Python script to prevent race conditions in GitHub Actions workflows where tags are created and subsequently deleted in the same run. It also adds actionlint and zizmor hooks to the pre-commit configuration and updates the project documentation. Feedback focused on improving the robustness of the regular expressions in the new script to better handle multi-line commands and alternative CLI flags.

Comment on lines +72 to +75
_TAG_CREATE_RE = re.compile(
r"gh\s+api[^\n]*git/refs[^/\w][\s\S]{0,300}?-f\s+ref=[\"']?refs/tags/",
re.MULTILINE,
)
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.

medium

The _TAG_CREATE_RE regex is brittle because it uses [^ ]* between gh api and the endpoint. This prevents matching commands split across multiple lines using backslashes (e.g., gh api \ git/refs ...), which is common in workflow files. Additionally, it assumes a specific order of arguments (endpoint before -f ref=). Consider using a more flexible pattern that allows newlines and different argument orders within a reasonable character window.

Suggested change
_TAG_CREATE_RE = re.compile(
r"gh\s+api[^\n]*git/refs[^/\w][\s\S]{0,300}?-f\s+ref=[\"']?refs/tags/",
re.MULTILINE,
)
_TAG_CREATE_RE = re.compile(
r"gh\s+api[\s\S]{0,500}?(?:git/refs[^/\w][\s\S]{0,300}?-f\s+ref=[\"']?refs/tags/|-f\s+ref=[\"']?refs/tags/[\s\S]{0,300}?git/refs[^/\w])",
re.MULTILINE,
)

Comment on lines +83 to +86
_TAG_DELETE_RE = re.compile(
r"gh\s+api\s+-X\s+DELETE[^\n]*git/refs/tags/",
re.MULTILINE,
)
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.

medium

The _TAG_DELETE_RE regex also suffers from the multi-line limitation due to [^ ]*. It also misses alternative flag names like --method DELETE or cases where the method flag appears after the endpoint. Furthermore, it doesn't account for gh release delete --cleanup-tag, which can also trigger the race condition described in #1818.

Suggested change
_TAG_DELETE_RE = re.compile(
r"gh\s+api\s+-X\s+DELETE[^\n]*git/refs/tags/",
re.MULTILINE,
)
_TAG_DELETE_RE = re.compile(
r"gh\s+(?:api[\s\S]{0,200}?(?:-X\s+DELETE|--method\s+DELETE)[\s\S]{0,200}?git/refs/tags/|release\s+delete[\s\S]{0,100}?--cleanup-tag)",
re.MULTILINE,
)

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
.github/workflows/finalize-release.yml (1)

149-158: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Split error handling for gh release view to distinguish transient failures from missing releases.

At this point both upstream workflows can already be complete, so a transient auth/rate-limit/API failure takes the same proceed=false branch as a genuine missing draft. The final reporter then leaves the commit pending, and there may be no later workflow_run left to retry publication. GitHub CLI exits non-zero on errors, but distinguishing the failure cause requires parsing stderr.

Suggested error split
-          if ! IS_DRAFT=$(gh release view "$TAG" --repo "$GITHUB_REPOSITORY" \
-              --json isDraft --jq '.isDraft' 2>/dev/null); then
-            echo "Release $TAG not found -- nothing to publish."
-            echo "proceed=false" >> "$GITHUB_OUTPUT"
-            exit 0
-          fi
+          release_err="$(mktemp)"
+          if IS_DRAFT=$(gh release view "$TAG" --repo "$GITHUB_REPOSITORY" \
+              --json isDraft --jq '.isDraft' 2>"$release_err"); then
+            :
+          elif grep -qiE 'not found|could not resolve' "$release_err"; then
+            echo "Release $TAG not found -- nothing to publish."
+            echo "proceed=false" >> "$GITHUB_OUTPUT"
+            exit 0
+          else
+            echo "::error::gh release view failed for $TAG"
+            cat "$release_err" >&2
+            exit 1
+          fi
🤖 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 @.github/workflows/finalize-release.yml around lines 149 - 158, Capture both
stdout and stderr when running gh release view for TAG in GITHUB_REPOSITORY
(e.g. IS_DRAFT=$(gh release view ... 2>err) or similar), then if gh exits
non-zero inspect the captured stderr: if it contains a “not
found”/404/release-not-found indicator treat it as a missing draft and write
"proceed=false" to GITHUB_OUTPUT, otherwise treat it as a transient/auth/API
error and fail the step (exit 1) so the workflow can be retried; keep references
to IS_DRAFT, gh release view, TAG, GITHUB_REPOSITORY and proceed=false when
implementing the checks.
🤖 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 @.github/workflows/dev-release.yml:
- Around line 237-252: The "Verify minted tag survived the run" step currently
only runs on success paths; update its if condition to include always() so the
check runs regardless of prior step failures—i.e., modify the step's if
expression for the step named "Verify minted tag survived the run" to prepend
always() (combined with the existing checks on steps.check-tag.outputs.skip,
steps.version.outputs.skip, and steps.tag-exists.outputs.skip) so the
tag-verification block executes even when the job failed.

In @.pre-commit-config.yaml:
- Around line 226-231: The new pre-commit hook entry with id
"workflow-tag-lifecycle" currently only triggers on changes to workflow YAMLs
(files: ^\.github/workflows/.*\.ya?ml$) which allows changes to the checker or
its config to bypass the gate; update the hook's files pattern to also include
the checker and its configuration (e.g., include
scripts/check_workflow_tag_lifecycle.py and the repo pre-commit config or any
related AST/config paths such as .pre-commit-config.yaml) so edits to
scripts/check_workflow_tag_lifecycle.py or the config will re-run the "entry:
uv" hook and prevent regression. Ensure you reference the existing id
"workflow-tag-lifecycle" when making the change.

In `@docs/reference/claude-reference.md`:
- Around line 101-102: Update the "Dev Release" doc paragraph to reflect the
current release-note format: change the described release title from "Dev build
`#N` toward vX.Y.Z" to use the minted tag variable ($DEV_TAG) and replace the
claim that the full commit body is copied with the current behavior that only
the short SHA and the commit subject are written into the --notes-file;
reference the dev-release.yml workflow, the DEV_TAG variable, the git log -1
usage and the gh release create --notes-file path so the wording matches the
actual implementation.

In `@scripts/check_workflow_tag_lifecycle.py`:
- Around line 77-86: _TAG_DELETE_RE currently only matches single-line "gh api
-X DELETE ...git/refs/tags/..." calls and can be bypassed by backslash-newline
continuations; update the regex used to compile _TAG_DELETE_RE so it tolerates
line continuations and newlines between tokens (e.g., allow backslash+newline or
other whitespace between "gh api -X DELETE" and "git/refs/tags/"), or compile
with a flag/construct that permits matching across lines, ensuring the pattern
still anchors to "gh api", "-X DELETE", and "git/refs/tags/" (reference:
_TAG_DELETE_RE).

---

Outside diff comments:
In @.github/workflows/finalize-release.yml:
- Around line 149-158: Capture both stdout and stderr when running gh release
view for TAG in GITHUB_REPOSITORY (e.g. IS_DRAFT=$(gh release view ... 2>err) or
similar), then if gh exits non-zero inspect the captured stderr: if it contains
a “not found”/404/release-not-found indicator treat it as a missing draft and
write "proceed=false" to GITHUB_OUTPUT, otherwise treat it as a
transient/auth/API error and fail the step (exit 1) so the workflow can be
retried; keep references to IS_DRAFT, gh release view, TAG, GITHUB_REPOSITORY
and proceed=false when implementing the checks.
🪄 Autofix (Beta)

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: Repository UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 5b98f6e6-5bca-4888-b5b3-de3b38c02c88

📥 Commits

Reviewing files that changed from the base of the PR and between ec746a9 and a4ce64b.

📒 Files selected for processing (8)
  • .github/workflows/ci.yml
  • .github/workflows/cli.yml
  • .github/workflows/dev-release.yml
  • .github/workflows/finalize-release.yml
  • .pre-commit-config.yaml
  • CLAUDE.md
  • docs/reference/claude-reference.md
  • scripts/check_workflow_tag_lifecycle.py
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: Lighthouse Site
  • GitHub Check: Dashboard Test
  • GitHub Check: Test (Python 3.14)
  • GitHub Check: Build Web Assets (melange)
  • GitHub Check: Analyze (python)
🧰 Additional context used
📓 Path-based instructions (2)
docs/**/*.md

📄 CodeRabbit inference engine (CLAUDE.md)

Numeric claims in README.md and public docs (docs/index.md, docs/roadmap/index.md, docs/architecture/decisions.md) about test count, release, Mem0 stars, provider count, subagent count must be sourced from data/runtime_stats.yaml via HTML-comment markers <!--RS:NAME-->display value<!--/RS-->

Static historical counts and illustrative scale numbers may carry per-line opt-out: <!-- lint-allow: doc-numeric-macros -- <reason> --> (reason mandatory)

Every implementation plan must be presented to the user for accept/deny before coding; be critical at every phase; surface improvements as suggestions, not silent changes; prioritize by dependency order, not priority labels

Files:

  • docs/reference/claude-reference.md
docs/**/*.{md,d2,mermaid}

📄 CodeRabbit inference engine (CLAUDE.md)

Use fenced code blocks with language tags: d2 for architecture/nested containers, mermaid for flowcharts/sequence/pipelines; use markdown tables for tabular data; never use text fences with ASCII box-drawing

Files:

  • docs/reference/claude-reference.md
🧠 Learnings (2)
📓 Common learnings
Learnt from: CR
Repo: Aureliolo/synthorg

Timestamp: 2026-05-08T22:16:26.335Z
Learning: Opt-in telemetry (off by default); every event property must be in `_ALLOWED_PROPERTIES` keyed by event type; unknown keys raise `PrivacyViolationError` and are dropped; never bypass the scrubber
Learnt from: CR
Repo: Aureliolo/synthorg

Timestamp: 2026-05-08T22:16:26.335Z
Learning: Use Git commits with format `<type>: <description>` (feat/fix/refactor/docs/test/chore/perf/ci); enforced by commitizen
Learnt from: CR
Repo: Aureliolo/synthorg

Timestamp: 2026-05-08T22:16:26.335Z
Learning: Signed commits required on protected refs (GPG/SSH signed or GitHub App-signed via `synthorg-repo-bot`); see [github-environments.md](docs/reference/github-environments.md#release_bot_app_)
Learnt from: CR
Repo: Aureliolo/synthorg

Timestamp: 2026-05-08T22:16:26.335Z
Learning: Use branch naming `<type>/<slug>` from main; see `.pre-commit-config.yaml` for pre-commit hooks (ruff, gitleaks, hadolint, no-em-dashes, no-redundant-timeout, check-single-migration-per-pr, check-no-modify-migration, no-release-please-token, workflow-shell-git-commits); eslint-web runs at pre-push only
Learnt from: CR
Repo: Aureliolo/synthorg

Timestamp: 2026-05-08T22:16:26.335Z
Learning: Hookify rules: `block-pr-create` (use `/pre-pr-review`), `block-double-push` (5-min throttle when open PR exists), `enforce-parallel-tests` (`-n 8`), `no-cd-prefix`, `no-local-coverage`
Learnt from: CR
Repo: Aureliolo/synthorg

Timestamp: 2026-05-08T22:16:26.335Z
Learning: Pre-push hooks: mypy + pytest (affected modules) + golangci-lint + go vet + go test (CLI) + eslint-web + conditional `orphan-fixtures`/`setting-to-startup-trace` gates; foundational module changes or conftest edits trigger full runs
Learnt from: CR
Repo: Aureliolo/synthorg

Timestamp: 2026-05-08T22:16:26.335Z
Learning: Use GitHub issue queries via `gh issue list`, NOT MCP `list_issues` (unreliable field data)
Learnt from: CR
Repo: Aureliolo/synthorg

Timestamp: 2026-05-08T22:16:26.335Z
Learning: Merge strategy: squash; PR body becomes squash commit message; trailers (`Release-As`, `Closes `#N``) must be in PR body to land
Learnt from: CR
Repo: Aureliolo/synthorg

Timestamp: 2026-05-08T22:16:26.335Z
Learning: Preserve existing `Closes `#NNN`` in PR issue references; never remove unless explicitly asked
Learnt from: CR
Repo: Aureliolo/synthorg

Timestamp: 2026-05-08T22:16:26.335Z
Learning: After finishing an issue: branch (`<type>/<slug>`), commit, push; do NOT auto-create a PR; ALWAYS use `/pre-pr-review` to create PRs (gh pr create is hookify-blocked); trivial/docs-only: `/pre-pr-review quick`; after PR exists, `/aurelio-review-pr` handles external reviewer feedback
Learnt from: CR
Repo: Aureliolo/synthorg

Timestamp: 2026-05-08T22:16:26.335Z
Learning: Fix EVERYTHING valid review agents find (including pre-existing issues in surrounding code, suggestions, and adjacent findings); no deferring, no 'out of scope'
Learnt from: CR
Repo: Aureliolo/synthorg

Timestamp: 2026-05-08T22:16:26.335Z
Learning: Any PR establishing or expanding a project-wide convention (error hierarchies, persistence boundary, mock-spec, regional defaults, typed boundary, settings-to-startup wiring, secret-log redaction, API-DTO `extra="forbid"`, no-magic-numbers, no-em-dashes, etc.) MUST include the AST/script gate preventing regression; PRs proposing convention without enforcement are rejected
Learnt from: CR
Repo: Aureliolo/synthorg

Timestamp: 2026-05-08T22:16:26.335Z
Learning: The machine-readable inventory of every MANDATORY paragraph lives in `scripts/convention_gate_map.yaml`; meta-gate `scripts/check_convention_gate_inventory.py` enforces every MANDATORY has either registered gate or explicit `exempt: { reason }` entry; adding new MANDATORY without updating YAML fails pre-push
📚 Learning: 2026-05-05T09:04:46.195Z
Learnt from: Aureliolo
Repo: Aureliolo/synthorg PR: 1760
File: scripts/_dual_backend_parity_lib.py:215-216
Timestamp: 2026-05-05T09:04:46.195Z
Learning: This repository targets Python 3.14+ and follows PEP 758. Therefore, reviewer tooling should NOT treat unparenthesized multi-exception `except` clauses written without an `as` clause (e.g., `except MemoryError, RecursionError:`) as syntax errors. Only flag `except`-clause problems when they are genuinely invalid for Python 3.14+.

Applied to files:

  • scripts/check_workflow_tag_lifecycle.py

Comment thread .github/workflows/dev-release.yml
Comment thread .pre-commit-config.yaml Outdated
Comment thread docs/reference/claude-reference.md Outdated
Comment thread scripts/check_workflow_tag_lifecycle.py Outdated
@codecov
Copy link
Copy Markdown

codecov Bot commented May 8, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 84.76%. Comparing base (d01e624) to head (05c40aa).
⚠️ Report is 1 commits behind head on main.
✅ All tests successful. No failed tests found.

Additional details and impacted files
@@           Coverage Diff           @@
##             main    #1827   +/-   ##
=======================================
  Coverage   84.76%   84.76%           
=======================================
  Files        1798     1798           
  Lines      104306   104306           
  Branches     9128     9128           
=======================================
+ Hits        88417    88418    +1     
+ Misses      13675    13673    -2     
- Partials     2214     2215    +1     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

dev-release.yml: add always() to 'Verify minted tag survived' so the regression guard runs on failure paths where tag loss is most likely. CodeRabbit major.

.pre-commit-config.yaml: self-protect workflow-tag-lifecycle hook by also triggering on edits to the checker script and the pre-commit config itself. CodeRabbit major.

docs/reference/claude-reference.md: update Dev Release paragraph -- release title is the minted tag (DEV_TAG), and notes-file carries only short SHA + commit subject (no full body). CodeRabbit minor.

finalize-release.yml: split gh release view error handling -- 'not found' is a missing draft (proceed=false), every other failure is transient (exit 1) so the next workflow_run retries instead of silently leaving the commit pending. CodeRabbit major outside-diff.

scripts/check_workflow_tag_lifecycle.py: broaden CREATE regex to tolerate backslash-newline continuations and reversed argument order (Gemini). Broaden DELETE regex to tolerate continuations, --method DELETE, and gh release delete --cleanup-tag (CodeRabbit + Gemini). Fix _SHELL_COMMENT_RE: backslash-s-star eats preceding blank-line newlines via cross-line greedy match, switch to character-class for space and tab so newlines are preserved. Add per-line opt-out machinery (# lint-allow: workflow-tag-lifecycle -- reason).

dev-release.yml: per-line opt-out on the bulk cleanup line with rationale (deletes 5+-revision-old tags whose downstream workflows have completed; not the just-minted tag).

tests/unit/scripts/test_check_workflow_tag_lifecycle.py: 18-test suite covering positive matches, negative cases, opt-out machinery, shell-comment scrubber line-number regression, and a repo-level smoke test.
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 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 @.github/workflows/finalize-release.yml:
- Around line 149-166: The current finalize-release step treats a transient "gh
release view" 404 as a permanent missing draft and sets proceed=false (using
variables release_err and IS_DRAFT with gh release view "$TAG"), which can leave
finalization stuck when upstream workflows already succeeded; change this to
perform bounded polling/retries: call gh release view "$TAG" in a loop with a
short sleep and a limited retry count, checking release_err / IS_DRAFT each
attempt, only treat "not found" as a skip after retries are exhausted, and if
still failing due to transient API errors after the retry limit, fail the job
(non-zero exit) so the run is visible and can be retried instead of silently
writing proceed=false.

In @.pre-commit-config.yaml:
- Around line 226-231: The pre-commit hook "workflow-tag-lifecycle" currently
relies on the default pass_filenames behavior which allows checker/config-only
edits to bypass the workflow scan; update the hook configuration for id
"workflow-tag-lifecycle" to include pass_filenames: false so the hook runs
against the full repository (or alternatively modify
scripts/check_workflow_tag_lifecycle.py to detect an empty/only-non-workflow
filename list and fall back to scanning all .github/workflows/*.yml/.yaml
files), ensuring the checker cannot be skipped by edits that only touch the
checker or .pre-commit-config.yaml.

In `@scripts/check_workflow_tag_lifecycle.py`:
- Around line 78-85: The _TAG_CREATE_RE currently only matches lowercase "-f"
flag; update its pattern to accept both "-f" and "-F" by replacing occurrences
of "-f" in the alternation with "-[fF]" (so both branches that contain
"-f\s+ref=..." become "-[fF]\s+ref=...") and run/add unit tests that include
typed-field forms like "gh api -F ref=refs/tags/v1 -F sha=... .../git/refs" to
ensure the regex now catches the typed-field form; the symbol to edit is
_TAG_CREATE_RE and add test cases covering "-F ref=refs/tags/..." usage.
🪄 Autofix (Beta)

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: Repository UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: f0b3ca24-f28d-417e-b576-4683873fc7d5

📥 Commits

Reviewing files that changed from the base of the PR and between a4ce64b and 6cdfce5.

📒 Files selected for processing (6)
  • .github/workflows/dev-release.yml
  • .github/workflows/finalize-release.yml
  • .pre-commit-config.yaml
  • docs/reference/claude-reference.md
  • scripts/check_workflow_tag_lifecycle.py
  • tests/unit/scripts/test_check_workflow_tag_lifecycle.py
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: Test (Python 3.14)
  • GitHub Check: Dashboard Test
  • GitHub Check: Build Web Assets (melange)
  • GitHub Check: Lighthouse Site
  • GitHub Check: Analyze (python)
🧰 Additional context used
📓 Path-based instructions (4)
.pre-commit-config.yaml

📄 CodeRabbit inference engine (CLAUDE.md)

Pre-commit hooks configured in .pre-commit-config.yaml: ruff, gitleaks, hadolint, no-em-dashes, no-redundant-timeout, check-single-migration-per-pr, check-no-modify-migration (bypass SYNTHORG_MIGRATION_SQUASH=1), no-release-please-token, workflow-shell-git-commits; eslint-web runs at pre-push only

Wire each new gate into .pre-commit-config.yaml (pre-commit or pre-push stage) with # lint-allow: <gate-name> -- <reason> per-line opt-outs

Files:

  • .pre-commit-config.yaml
**/*.md

📄 CodeRabbit inference engine (CLAUDE.md)

Use fenced code blocks with d2 language tag for architecture/nested container diagrams in documentation

Use mermaid language tag for flowcharts, sequence diagrams, and pipeline diagrams in documentation

Use markdown tables for tabular data in documentation; never use text fences with ASCII box-drawing

D2 diagrams in documentation must use theme 200 (Dark Mauve) with dark-only configuration as specified in mkdocs.yml

Files:

  • docs/reference/claude-reference.md
**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

Never use from __future__ import annotations (Python 3.14 has native PEP 649 lazy annotations)

Use PEP 758 except syntax: except A, B: (no parens) when not binding; as exc requires parens

Files:

  • tests/unit/scripts/test_check_workflow_tag_lifecycle.py
  • scripts/check_workflow_tag_lifecycle.py
tests/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

Every test must declare spec=ConcreteClass on Mock()/AsyncMock()/MagicMock() (enforced by gate); pre-existing sites frozen in baseline; regenerate via --update flag

Use mock_dispatcher from tests/conftest.py (AsyncMock(spec=NotificationDispatcher)) for shared mocks in tests

Time-driven tests: import FakeClock from tests._shared.fake_clock; inject via clock= parameter; FakeClock.sleep advances virtual time and yields once via asyncio.sleep(0)

Test markers: @pytest.mark.unit / integration / e2e / slow; run with -n 8 parallelism always using --dist=loadfile

Global test timeout: 30s per test in pyproject.toml; non-default like timeout(60) is allowed per-test

Never monkeypatch.setattr(module.logger, "info", spy) (stale bound method caching); use try/finally del proxy.<level> instead; see _logger_info_spy in test examples

Parametrize similar test cases via @pytest.mark.parametrize

Property-based testing via Hypothesis (Python) with 10 deterministic examples in CI (derandomize=True); Hypothesis failures are real bugs—fix the bug and add @example(...) decorator

Never skip/xfail/dismiss flaky tests; fix fundamentally. FakeClock-first when the class accepts clock=. For 'block until cancelled', use asyncio.Event().wait() not asyncio.sleep(large)

Files:

  • tests/unit/scripts/test_check_workflow_tag_lifecycle.py

⚙️ CodeRabbit configuration file

Test files do not require Google-style docstrings on classes or functions -- ruff D rules are only enforced on src/. A bare @settings() decorator with no arguments on Hypothesis property tests is a no-op and should not be suggested -- the HYPOTHESIS_PROFILE env var controls example counts via registered profiles, which @given() honors automatically.

Files:

  • tests/unit/scripts/test_check_workflow_tag_lifecycle.py
🧠 Learnings (2)
📓 Common learnings
Learnt from: CR
Repo: Aureliolo/synthorg

Timestamp: 2026-05-08T22:59:37.235Z
Learning: Read the relevant `docs/design/` page before implementing or planning; deviations require explicit user approval and design page updates
Learnt from: CR
Repo: Aureliolo/synthorg

Timestamp: 2026-05-08T22:59:37.235Z
Learning: Every implementation plan must be presented to the user for accept/deny before coding; surface improvements as suggestions, not silent changes; prioritize by dependency order, not priority labels
Learnt from: CR
Repo: Aureliolo/synthorg

Timestamp: 2026-05-08T22:59:37.235Z
Learning: CI generates and injects runtime stats from `data/runtime_stats.yaml` BEFORE `zensical build`; regenerate via `scripts/generate_runtime_stats.py` and `scripts/inject_runtime_stats.py`
Learnt from: CR
Repo: Aureliolo/synthorg

Timestamp: 2026-05-08T22:59:37.235Z
Learning: Minimum 80% code coverage required in CI (benchmarks excluded); coverage enforcement in pytest runs
Learnt from: CR
Repo: Aureliolo/synthorg

Timestamp: 2026-05-08T22:59:37.235Z
Learning: Commit messages follow `<type>: <description>` format (feat/fix/refactor/docs/test/chore/perf/ci); enforced by commitizen
Learnt from: CR
Repo: Aureliolo/synthorg

Timestamp: 2026-05-08T22:59:37.235Z
Learning: Signed commits required on every protected ref: GPG/SSH signed OR GitHub App-signed via `synthorg-repo-bot` (Git Data API with installation token)
Learnt from: CR
Repo: Aureliolo/synthorg

Timestamp: 2026-05-08T22:59:37.235Z
Learning: Branch names: `<type>/<slug>` from main
Learnt from: CR
Repo: Aureliolo/synthorg

Timestamp: 2026-05-08T22:59:37.235Z
Learning: Hook rules in `.claude/hookify.*.md`: `block-pr-create` (use `/pre-pr-review`), `block-double-push` (5-min throttle; opt-out via `.claude/state/allow-double-push.flag`), `enforce-parallel-tests` (`-n 8`), `no-cd-prefix`, `no-local-coverage`
Learnt from: CR
Repo: Aureliolo/synthorg

Timestamp: 2026-05-08T22:59:37.235Z
Learning: Pre-push hooks: mypy + pytest (affected) + golangci-lint + go vet + go test (CLI) + eslint-web + orphan-fixtures (opt-in `SYNTHORG_CHECK_ORPHAN_FIXTURES=1`) + setting-to-startup-trace (conditional); foundational module changes or conftest edits trigger full runs
Learnt from: CR
Repo: Aureliolo/synthorg

Timestamp: 2026-05-08T22:59:37.235Z
Learning: GitHub issue queries: use `gh issue list` via Bash, NOT MCP `list_issues` (unreliable field data)
Learnt from: CR
Repo: Aureliolo/synthorg

Timestamp: 2026-05-08T22:59:37.235Z
Learning: PR merge strategy: squash; PR body becomes squash commit message; trailers (`Release-As`, `Closes `#N``) must be in PR body to land
Learnt from: CR
Repo: Aureliolo/synthorg

Timestamp: 2026-05-08T22:59:37.235Z
Learning: Preserve existing `Closes `#NNN`` in PR issue references; never remove unless explicitly asked
Learnt from: CR
Repo: Aureliolo/synthorg

Timestamp: 2026-05-08T22:59:37.235Z
Learning: After finishing an issue: branch (`<type>/<slug>`), commit, push; do NOT auto-create a PR
Learnt from: CR
Repo: Aureliolo/synthorg

Timestamp: 2026-05-08T22:59:37.235Z
Learning: Always use `/pre-pr-review` to create PRs (`gh pr create` is hookify-blocked); trivial/docs-only: `/pre-pr-review quick`
Learnt from: CR
Repo: Aureliolo/synthorg

Timestamp: 2026-05-08T22:59:37.235Z
Learning: After PR exists, `/aurelio-review-pr` handles external reviewer feedback; fix EVERYTHING valid review agents find, including pre-existing issues in surrounding code
Learnt from: CR
Repo: Aureliolo/synthorg

Timestamp: 2026-05-08T22:59:37.235Z
Learning: Any PR establishing or expanding a project-wide convention MUST include the AST/script gate preventing regression; PRs proposing conventions without enforcement are rejected; gate catches the SECOND occurrence; audit finds the FIRST
Learnt from: CR
Repo: Aureliolo/synthorg

Timestamp: 2026-05-08T22:59:37.235Z
Learning: When tests fail due to timeout/slowness/contention: NEVER delete, skip, or `xfail`; NEVER `--no-verify`; NEVER edit `tests/baselines/unit_timing.json`; fix source code instead
Learnt from: CR
Repo: Aureliolo/synthorg

Timestamp: 2026-05-08T22:59:37.235Z
Learning: Isolation regression gate: `scripts/run_affected_tests.py` re-runs affected subset under `pytest-repeat --count 2` after green pass; real failures block gate; scattered native crashes are advisory; opt-out via `SYNTHORG_SKIP_ISOLATION_GATE=1`
📚 Learning: 2026-05-05T09:04:46.195Z
Learnt from: Aureliolo
Repo: Aureliolo/synthorg PR: 1760
File: scripts/_dual_backend_parity_lib.py:215-216
Timestamp: 2026-05-05T09:04:46.195Z
Learning: This repository targets Python 3.14+ and follows PEP 758. Therefore, reviewer tooling should NOT treat unparenthesized multi-exception `except` clauses written without an `as` clause (e.g., `except MemoryError, RecursionError:`) as syntax errors. Only flag `except`-clause problems when they are genuinely invalid for Python 3.14+.

Applied to files:

  • tests/unit/scripts/test_check_workflow_tag_lifecycle.py
  • scripts/check_workflow_tag_lifecycle.py

Comment thread .github/workflows/finalize-release.yml Outdated
Comment thread .pre-commit-config.yaml
Comment thread scripts/check_workflow_tag_lifecycle.py
finalize-release.yml: replace single-shot 'not found' skip with bounded polling. Both upstream Docker + CLI workflows are already complete by this point so a transient release-page 404 (visibility lag for a brand-new release) needs to be retried before being treated as a legitimate skip; without retries, the run silently writes proceed=false and there is no later workflow_run event to retry on, leaving finalize-release pending. 6 attempts with linear backoff (5s + 10s + 15s + 20s + 25s + 30s = 105s budget) before giving up. Non-404 transient errors still fail-fast non-zero so the run is visible.

.pre-commit-config.yaml: add pass_filenames: false to the workflow-tag-lifecycle hook AND pass --scan-all explicitly so an edit limited to the checker script or to .pre-commit-config.yaml itself can no longer bypass the gate. Without this, the default pass_filenames=true would invoke the script with only the non-workflow path; the script would skip it on the .yml/.yaml suffix filter and exit 0 with zero workflows scanned.

scripts/check_workflow_tag_lifecycle.py: widen _TAG_CREATE_RE to match both -f and -F field flags. gh api -F (typed field) POSTs to the same endpoint as -f (raw string), so a workflow using gh api -F ref=refs/tags/v1 ... git/refs would create a tag identically and bypass the gate. Both alternation branches now use -[fF].

tests: add 2 cases for the typed-field -F form (single-line + reversed argument order). Total now 20 tests.
@Aureliolo Aureliolo merged commit b7b9a59 into main May 8, 2026
86 checks passed
@Aureliolo Aureliolo deleted the fix/dev-release-tag-race branch May 8, 2026 23:41
@Aureliolo Aureliolo temporarily deployed to cloudflare-preview May 8, 2026 23:41 — with GitHub Actions Inactive
Aureliolo pushed a commit that referenced this pull request May 10, 2026
<!-- HIGHLIGHTS_START -->
## Highlights

> _AI-generated summary (model: `openai/gpt-4.1-mini` via GitHub
Models). Commit-based changelog below._

### What you'll notice
- Improved error logging and Prometheus instrumentation provide better
system monitoring.
- Eliminated race conditions in CI tagging for more reliable development
releases.
- Fixed critical configuration access and kill-switch bugs to enhance
system stability.
- Enhanced client experience with retry-after headers and better
websocket reconnect behavior.

### What's new
- Introduced composite indexes and cursor pagination for faster data
queries.
- Added server-sent events rate limiting and Ollama input sanitization
for improved security.

### Under the hood
- Centralized workflow error mappings to standardize error handling.
- Refactored API lifecycle fallback to use a configuration snapshot for
consistency.
- Tightened startup settings baseline and reduced controller error
baseline to zero.
- Replaced flaky contributor-assistant GitHub action with a custom
stable step.
- Consolidated Renovate dependency groups to avoid update conflicts.
- Upgraded in-toto-golang dependency to fix security vulnerabilities and
dropped unnecessary CVE waivers.
- Extensive lock file maintenance and multiple infrastructure and Python
dependency updates.

<!-- HIGHLIGHTS_END -->

:robot: I have created a release *beep* *boop*
---


##
[0.8.2](v0.8.1...v0.8.2)
(2026-05-10)


### Features

* close audit gaps in error logging and Prometheus instrumentation
([#1821](#1821))
([ef00fdc](ef00fdc))


### Bug Fixes

* **ci:** eliminate dev-release tag-vs-downstream race + CI hygiene
audit ([#1827](#1827))
([b7b9a59](b7b9a59))
* **config:** close 6 settings reachability + kill-switch gaps
([#1798](#1798))
([410cb3b](410cb3b))
* correctness / safety fixes from 2026-05-05 audit (Wave 28)
([#1823](#1823))
([d01e624](d01e624))


### Performance

* composite indexes + cursor pagination + clock seam + SSE rate-limit +
Ollama sanitization + retry-after web client + WS reconnect jitter
([#1822](#1822))
([d1faf86](d1faf86))


### Refactoring

* **api:** move activities lifecycle-cap fallback to ApiBridgeConfig
snapshot ([#1840](#1840))
([7a56e9c](7a56e9c))
* centralise workflow error mapping and shared error codes
([#1778](#1778) sub-tasks A
+ E) ([#1843](#1843))
([11132cd](11132cd))
* drive controller-error baseline to zero
([#1778](#1778) sub-task A
tail) ([#1846](#1846))
([e96ae20](e96ae20))
* slim CLAUDE.md, port pr-review-toolkit agents, sync .opencode parity
([#1833](#1833))
([e6372b8](e6372b8))
* tighten settings → startup-trace baseline (8 → 0)
([#1847](#1847))
([3376ee2](3376ee2))


### Documentation

* fix CLAUDE.md inaccuracies and drop drift-prone counts
([#1844](#1844))
([371925f](371925f))


### Tests

* replace test placeholders with real subsystem wiring
([#1845](#1845))
([ddbb666](ddbb666))


### CI/CD

* **cla:** replace flaky contributor-assistant action with custom
read-path step
([#1819](#1819))
([11aeafe](11aeafe))
* tidy dev-release notes + stagger renovate lockfile day
([#1824](#1824))
([ec746a9](ec746a9))


### Maintenance

* cleanup roundup, sub-tasks a/c/d/g/h/j/l/m of
[#1781](#1781)
([#1838](#1838))
([099b871](099b871))
* close remaining 5 sub-tasks of
[#1781](#1781) (b/e/f/i/k)
([#1852](#1852))
([59cf0b2](59cf0b2))
* collapse Renovate dep groups into Python / Web / Infrastructure to
remove cross-PR overlap
([#1813](#1813))
([4cbd857](4cbd857))
* **deps,security:** bump in-toto-golang v0.11.0 + drop two patched CVE
waivers ([#1851](#1851))
([0b8b5bb](0b8b5bb))
* disable Renovate vulnerabilityAlerts so security flows into normal
updates ([#1834](#1834))
([6b7d15f](6b7d15f))
* Lock file maintenance
([#1820](#1820))
([ccbad73](ccbad73))
* Lock file maintenance
([#1842](#1842))
([13b68a5](13b68a5))
* Lock file maintenance
([#1853](#1853))
([db6650b](db6650b))
* Update dhi.io/nats:2.14-debian13 Docker digest to eb768bf
([#1841](#1841))
([37f84fc](37f84fc))
* Update Infrastructure dependencies
([#1815](#1815))
([75b12fe](75b12fe))
* Update Infrastructure dependencies
([#1831](#1831))
([3f3c50b](3f3c50b))
* Update Python dependencies
([#1817](#1817))
([e11332f](e11332f))
* Update Python dependencies
([#1832](#1832))
([4515c8e](4515c8e))

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).

---------

Co-authored-by: synthorg-repo-bot[bot] <279117679+synthorg-repo-bot[bot]@users.noreply.github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
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.

ci(dev-release): tag created before release object causes downstream CLI/Docker checkout 404 when release-create fails

1 participant