Skip to content

feat(reindex): add an inert, triple-gated reaper for abandoned index generations - #2552

Open
BigSimmo wants to merge 8 commits into
mainfrom
claude/reindex-reaper
Open

feat(reindex): add an inert, triple-gated reaper for abandoned index generations#2552
BigSimmo wants to merge 8 commits into
mainfrom
claude/reindex-reaper

Conversation

@BigSimmo

@BigSimmo BigSimmo commented Sep 2, 2026

Copy link
Copy Markdown
Owner

Summary

  • Add the missing automation for abandoned reindex generations — shipped inert. cleanup_abandoned_document_index_generations has exactly one caller, scripts/cleanup-abandoned-reindex-generations.ts via npm run reindex:cleanup-staged. No cron, no Railway cron, no CI job references it. Any crash mid-reindex leaves staged rows under a never-committed index_generation_id, plus storage objects, that nothing revisits — unbounded growth that depends on an operator remembering to run a janitor.
  • New .github/workflows/reindex-reaper.yml, modelled on ingestion-autopilot.yml, this repository's already-solved version of the same problem. Weekly schedule, repository_dispatch (never workflow_dispatch — that trigger lets a same-repository writer point a secret-bearing workflow at an arbitrary branch, and ingestion-autopilot.yml records that reasoning), concurrency with cancel-in-progress: false, permissions: contents: read plus issues: write, project ref pinned in env:, check:supabase-project before any RPC call, digest-pinned actions throughout, and an open-or-update alert issue when the probe finds leaked rows.
  • New --alert-on-abandoned flag on the cleanup script: after a dry run, exit non-zero when abandoned rows are found, so a read-only probe can raise an alert. The decision is a pure function in src/lib/reindex-pipeline.ts so it is unit-testable without touching Supabase. The apply path and the interactive confirm() brake are unchanged.
  • A security review found the static rule that guards all this did not work, and the second commit rewrites it. The first version checked for gate names, not gates: it fired on a line containing --apply and then only asserted that the two gate strings appeared somewhere in the file. Five ways past it were demonstrated, including a trailing # comment on the same line — the exact trick the rule's own comment claimed to have fixed — and ${{ … || 'true' }} defaults that fully arm both gates while leaving their names in place. Worse, its own self-test fixture declared "declare two env vars, delete unconditionally" to be correctly gated.
  • It is now a structural check, in two stages. Reachability: quote-aware inline comment stripping, backslash continuations joined, env: and shell assignments expanded, and npm aliases resolved transitively out of package.json so renaming the script cannot evade it. Gating: the apply invocation must sit lexically inside a shell conditional testing both gates; a gate expression that can render true on its own counts as defeated, not satisfied; the job-level enable gate is required; and push / pull_request / pull_request_target / workflow_dispatch are refused on any file that can reach the apply path. Unparsable shapes fail closed. Nine fixtures, eight of which must fail.
  • The scheduled alert can now tell "rows are leaking" from "the job broke". Previously any prior step failure — a failed install, a missing secret, unreachable Supabase — produced the same issue as a real detection. The probe's exit code is captured and the issue body branches on it, and a broken probe still shows red rather than passing silently.

No migration file is added and no SQL is applied to any database.

Why the destructive path stays behind three switches

The RPC is more destructive than "cleanup" suggests. Its delete predicate is "artifact row carries a generation id that differs from the document's current metadata.index_generation_id", and is distinct from NULL is true for any non-null left side — so a document whose metadata key is missing has every generation-bearing artifact row eligible, across seven tables. There is no owner scoping and no keep-newest fallback, and p_limit caps documents (up to 1000), not rows. Its real brakes are narrow: p_dry_run defaults true, execute is granted to service_role only, and there is a re-check for open ingestion jobs immediately before deleting.

So deletion requires all three of:

  1. vars.REINDEX_REAPER_ENABLED == 'true' — gates the whole job. Unset. Setting only this arms a read-only weekly probe.
  2. github.event.client_payload.apply == 'true' — per-run intent, reachable only via repository_dispatch.
  3. vars.REINDEX_REAPER_APPLY == 'true' — standing repository consent. Unset.

The shell evaluates the scheduled branch first and does not fall through, so cron reaches only --alert-on-abandoned; --apply appears exactly once in the file, behind the branch requiring both 2 and 3.

Merging this changes nothing. With REINDEX_REAPER_ENABLED unset the job-level if: is evaluated before any step runs, so the weekly run is a skipped job with zero steps — no checkout, no install, no check:supabase-project, no RPC — and a dispatch is a no-op. Arming either switch is a repository-settings change only the owner can make, and nothing in this diff can make it.

Verification

  • npm run verify:pr-local

Verification not run: verify:pr-local was not invoked as a wrapper. Its constituent gates were run individually and are quoted below.

Decisive output:

$ npm run check:github-actions
GitHub Actions pin check self-test passed.
GitHub Actions pin check passed.

$ npm run check:ci-scope
CI change scope self-test passed.

$ node scripts/run-vitest.mjs run tests/reindex-reaper-workflow.test.ts tests/reindex-reaper-alert.test.ts
 Test Files  2 passed (2)
      Tests  16 passed (16)

$ npm run test
 Test Files  949 passed (949)
      Tests  12071 passed | 1 skipped (12072)

$ npm run docs:check-inventory -> Docs inventory current
$ npm run docs:check-scripts   -> docs script-ref check passed
$ npm run lint      -> [gate-receipts] recorded a pass for "lint:internal" (6002 input files)
$ npm run typecheck -> [gate-receipts] recorded a pass for "typecheck:internal" (6002 input files)

The rewritten rule was re-verified independently against the real workflow, not only against fixtures — replacing the dispatch gate with a hardcoded true is caught, and the message names the line and quotes the conditional it did find:

GitHub Actions pin check failed:
- .github/workflows/reindex-reaper.yml: the reindex reaper apply path deletes generation-bearing
  artifact rows across seven tables for every tenant. Line 168 is not guarded by
  github.event.client_payload.apply (the per-run trusted-dispatch gate); its enclosing
  conditional tests: is_true " true " && is_true " ${{ vars.REINDEX_REAPER_APPLY }} ".

Deleting the job-level enable gate and adding a workflow_dispatch trigger are each caught the same way; the file was restored after each probe.

What was NOT verified. The workflow has never executed — verifying that would mean arming it against the live clinical database, which is exactly what this PR declines to do. The evidence is the contract test, the static rule, and a reading of the YAML.

  • npm run verify:ui — not applicable, no UI, routing, styling or browser behaviour changed.
  • npm run verify:release — not claimed.
  • npm run check:production-readinessnot run: provider-backed, and this batch does not contact Supabase. No runtime, deployment, or startup behaviour changes; the workflow is inert and the script flag only affects an exit code on the existing dry-run path.

Risk and rollout

  • Risk: None on merge, by construction — with the repository variables unset the job never starts. The risk is entirely in arming it later, which is why deletion needs three switches and why the static rule exists to stop a future PR removing one. The residual risk of not arming it is the one already recorded: rows and storage keep accumulating.
  • Rollback: Revert the commits. Nothing is scheduled, nothing is armed, and no state is created.
  • Provider or production effects: None while the repository variables are unset. Arming REINDEX_REAPER_ENABLED would begin a weekly read-only probe against the live Supabase project; arming REINDEX_REAPER_APPLY as well would permit deletion on an explicit dispatch. Both are explicit owner actions in repository settings and are not authorized by this PR.
  • RAG impact: none.

Clinical Governance Preflight

  • Source-backed claims still require linked source verification before clinical use
  • No patient-identifiable document workflow was introduced or expanded without explicit governance approval
  • Supabase target remains Clinical KB Database (sjrfecxgysukkwxsowpy)
  • Service-role keys and private document access remain server-only
  • Demo/synthetic content remains clearly separated from real clinical sources
  • Source metadata, review status, and outdated/unknown-source behavior remain conservative
  • Deployment classification/TGA SaMD impact was checked when clinical decision-support behavior changed

The project ref is pinned in the workflow's env: and check:supabase-project runs before any RPC call; the stale ref does not appear. The service-role key is referenced only as a repository secret inside the workflow and never leaves the runner, and the destructive RPC remains service_role-only. No clinical content, retrieval behaviour, source metadata, or decision-support behaviour changes, so the SaMD classification is unaffected.

Notes

This does not close #Q4Y7TR. The recorded fix there is for the commit RPC to enqueue storage_cleanup_jobs rows for superseded generations at commit time, where it already knows both generation ids. That is a migration, and a migration merged here reaches the live clinical database within seconds, so it belongs to a separately approved batch. What lands here is the Postgres-row detection half plus a gated apply path; the storage-object half of the leak is untouched. That is stated in the workflow header, in the library doc comment, and in the commit message so it cannot be mistaken for a closed issue.

Read this before arming it — the review findings recorded rather than coded around. They are the ones that matter for your decision.

"Three gates" is arithmetically true but two of them are standing state. Once REINDEX_REAPER_APPLY is set it stays set, and the only per-run control left is a repository_dispatch, which needs just contents: write — held by every collaborator token and by at least two agent-driven workflows in this repository. The genuinely safe way to arm the apply path is a GitHub Environment with required reviewers on a separate apply job. No environment: key was added on purpose: naming an environment that does not exist yet makes GitHub auto-create it unprotected, which would read as safer than it is. Create the protected environment first; that ordering is stated in the workflow header.

The weekly probe may well be permanently red. The RPC treats every generation-bearing row as abandoned on any document whose metadata.index_generation_id is absent — so documents predating generation stamping, first ingests that crashed before commit, and any metadata edit dropping the key would register as abandoned forever. That is a weekly alarm with no way to clear it short of fixing the data, and the remediation it prompts is the destructive path. Worth confirming against the live counts before arming even the read-only switch. The safer shapes, if it turns out that way, are reporting counts to a summary and alerting only on a delta, or excluding metadata-key-absent documents until a migration backfills them.

The apply path has no owner scoping and no row ceiling. Seven deletes in one transaction under a 180-second statement timeout. That is the RPC's pre-existing defect, not this diff's, but it is why arming should not be a bare repository variable: a migration adding a row ceiling and a keep-most-recent-generation guard should land before REINDEX_REAPER_APPLY is ever set.

Concurrency serialises reaper against reaper only. Nothing prevents a reaper run overlapping a reindex; the RPC's open-job re-check narrows that window rather than closing it.

Why repository_dispatch rather than the manual button. A workflow_dispatch on a secret-bearing workflow lets a writer choose the branch, and therefore the workflow definition, that runs with the service-role key. ingestion-autopilot.yml already carries that reasoning in a comment; this file follows it. Worth noting for whoever next touches the autopilot: its apply branch is evaluated first, so it would apply on a scheduled run if both its gates were open. The reaper's schedule-first ordering avoids that. Not changed here.

Refs #Q4Y7TR — the detection half only.

🤖 Generated with Claude Code

https://claude.ai/code/session_015sjekpEw82gMp57C8xzSxZ


Generated by Claude Code


Note

Low Risk
No runtime or database change on merge while repo variables stay unset; future risk is operational if the reaper is armed, because the workflow can call a cross-tenant destructive cleanup RPC with the service-role key.

Overview
Adds abandoned reindex generation detection that ships inert until REINDEX_REAPER_ENABLED is set: a new reindex-reaper.yml workflow (weekly schedule + repository_dispatch only) runs npm run reindex:cleanup-staged as a read-only probe and can open/update a reindex-reaper issue when abandoned rows are found.

The cleanup CLI gains --alert-on-abandoned, backed by abandonedReindexGenerationAlertExitCode in reindex-pipeline.ts (exit 2 on dry-run detection). Scheduled runs use that flag; --apply stays behind three switches (enable var, dispatch client_payload.apply, and REINDEX_REAPER_APPLY) with cron blocked from reaching apply.

check-github-action-pins.mjs is expanded with a structural guard so workflows cannot reach the reaper apply path without both apply gates, the job enable gate, and allowed triggers—plus executable fixtures and workflow contract tests. An outstanding-issues inbox row records that storage object cleanup (storage_cleanup_jobs) is still out of scope.

Reviewed by Cursor Bugbot for commit 64ede4a. Configure here.

…generations

`cleanup_abandoned_document_index_generations` has exactly one caller
(`npm run reindex:cleanup-staged`) and no schedule, so a crash mid-reindex leaks
staged rows and storage objects that nothing revisits. This ships the DETECTION
half plus an apply path that cannot fire without two independent switches.

- `--alert-on-abandoned`: a read-only dry run that finds abandoned rows now exits
  non-zero so a scheduled probe can alert. The apply path and the interactive
  `confirm()` brake are untouched. The decision is a pure exported function,
  `abandonedReindexGenerationAlertExitCode`, unit-tested with no network.
- `.github/workflows/reindex-reaper.yml`: `repository_dispatch` + a weekly cron,
  with no `workflow_dispatch`/`push`/`pull_request`/`pull_request_target`. The job
  is gated on `vars.REINDEX_REAPER_ENABLED`, which is deliberately unset, so
  merging this changes nothing and scheduled runs skip. Deletion additionally
  requires BOTH `github.event.client_payload.apply` AND `vars.REINDEX_REAPER_APPLY`;
  the scheduled branch is evaluated first, so cron can never reach `--apply`.
  Dry run is the default on every path.
- `check-github-action-pins.mjs`: a static rule refusing any workflow that can
  invoke the reaper's apply path without both gates, so a later PR cannot quietly
  arm it. Comments are stripped before matching — an early draft passed a workflow
  whose payload gate had been replaced by a hardcoded `true` purely because the
  header prose still named it. Self-test covers single-gated, comment-only-gated,
  and correctly double-gated fixtures.

The apply path is destructive well beyond what "cleanup" suggests: the RPC deletes
generation-bearing artifact rows across seven tables for every tenant, with no owner
scoping and no keep-newest fallback, and `p_limit` caps documents rather than rows.
A document missing `metadata.index_generation_id` makes all of its artifact rows
eligible. That is why nothing here is armed.

This does NOT close #Q4Y7TR. The recorded fix there is for the commit RPC to write
`storage_cleanup_jobs` rows for superseded generations at commit time; that is a
migration, it deploys to the live clinical database on merge, and it belongs to a
separate batch. No migration, schema, or SQL change is included here.

Verified offline: check:github-actions (incl. self-test, plus a negative proof that
removing one gate fails the rule), check:ci-scope, lint, typecheck, full unit suite
(949 files, 12068 passed), and the docs index/link/script/inventory checks. No
provider-backed command was run.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015sjekpEw82gMp57C8xzSxZ
…names

The static rule added in f61f83b asserted only that the strings
`github.event.client_payload.apply` and `vars.REINDEX_REAPER_APPLY` appeared
somewhere in a workflow's non-comment lines. It never checked that they gated
anything. A security review copied the two regexes into a harness and got past
them six ways; three of those armed the destructive path outright, and one was
certified as correct by the rule's own self-test.

The rule now answers a structural question instead of a textual one.

REACHABILITY. Inline `#` comments are stripped with quote awareness rather than
whole-line, so a trailing comment on the apply line no longer supplies the gate
names. Backslash continuations are joined, `env:` mappings and shell assignments
are expanded, and npm aliases are resolved transitively out of package.json, so
`--apply` behind a continuation, behind `$FLAGS`, or behind a renamed script is
still found. `reindex:cleanup-staged` stays a hardcoded floor so deleting the
alias cannot disable the rule.

GATING. An apply invocation must sit lexically inside a shell conditional whose
condition tests both gates; declaring the env vars and then deleting
unconditionally is now a failure. A gate expression that can render "true" on
its own (`|| 'true'`) is a defeated gate, not a satisfied one. The job-level
`vars.REINDEX_REAPER_ENABLED == 'true'` gate is required, and any file that can
reach the apply path is refused a `push`, `pull_request`, `pull_request_target`
or `workflow_dispatch` trigger. Every unparsable shape fails closed, and the
guard covers the class of workflows rather than the one path the contract test
pins.

Self-test fixtures are rewritten as A-I: trailing comment, line continuation,
env indirection, npm alias, single gate, declared-but-not-gated, `|| 'true'`
defaults, correctly gated (the only must-pass), and a manual trigger, plus the
comment-only and missing-enable-gate cases. `--self-test --explain` prints each
fixture's verdict.

The workflow changes with it:

- The scheduled alert could not tell "rows are leaking" from "the job broke":
  `failure()` fired on a failed `npm ci` or an unreachable Supabase just as it
  did on detection. The probe's exit code carries that distinction already (2 is
  detection, 1 is a real error), so the step now captures it via
  `$GITHUB_OUTPUT` under `continue-on-error`, the issue body branches on it, and
  a final step re-raises so a broken probe cannot report green.
- Gate 1 is a GitHub expression and compares case-insensitively, while gates 2
  and 3 were POSIX `=` compares, so arming with `TRUE` gave a silent dry run.
  Both are normalised through `is_true`, and a value that is neither true nor
  false now raises a `::warning::` instead of being swallowed.
- The header documented gate 2 as `client_payload.apply == 'true'`, which is
  stricter than the shell string compare actually implemented; the prose now
  matches the code.

Four limits are recorded in the header rather than coded around: two of the
three gates are standing repository state and only `client_payload.apply` is
per-run, so the safe way to arm the apply path is a GitHub Environment with
required reviewers on a separate apply job (deliberately not referenced yet,
since naming a missing environment auto-creates it unprotected); the probe will
very likely be permanently red because documents missing
`metadata.index_generation_id` register as abandoned forever; the RPC still has
no owner scoping and no row ceiling; and the concurrency group serialises reaper
against reaper only, not against a live reindex. A `docs/outstanding-issues-inbox`
request records that only the detection half landed.

Verified offline: check:github-actions (self-test passed, and the real workflow
was re-checked after removing each gate in turn — all three negative proofs
fire), check:ci-scope, the two reaper test files (16 passed), lint, typecheck,
full unit suite (949 files, 12071 passed, 1 skipped), knip, and the docs
index/inventory/scripts/links checks. No migration, no SQL, no provider-backed
command, and the reaper was never run.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015sjekpEw82gMp57C8xzSxZ
@coderabbitai

coderabbitai Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Team

Run ID: dc3a7d3d-276a-418b-b916-3fa2e6352d42


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.

@supabase

supabase Bot commented Sep 2, 2026

Copy link
Copy Markdown

This pull request has been ignored for the connected project sjrfecxgysukkwxsowpy because there are no changes detected in supabase directory. You can change this behaviour in Project Integrations Settings ↗︎.


Preview Branches by Supabase.
Learn more about Supabase Branching ↗︎.

@BigSimmo
BigSimmo marked this pull request as ready for review September 2, 2026 06:34
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 2, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-09-02T06:41:52.585681Z 3e3674a Draft marked ready
ℹ️ 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" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@cursor

cursor Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: serverGenReqId_6874f36a-9927-481b-8537-7a832496254e)

@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: 3e3674aa7d

ℹ️ 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 scripts/check-github-action-pins.mjs
Comment thread scripts/check-github-action-pins.mjs Outdated
@BigSimmo

BigSimmo commented Sep 2, 2026

Copy link
Copy Markdown
Owner Author

@codex resolve actionable Codex review findings for this pull request and current head using the repository instructions. This is the pull request's single automatic repair pass: do not perform a fresh review, create new standalone findings, or request another review. Work only the existing unresolved Codex threads on the current head. The only repository destination is BigSimmo/Database, and the only branch destination is the pull request head branch claude/reindex-reaper at starting commit db0b026; never publish fixes to a detached or synthetic work branch and never create a stacked pull request. Use the authenticated GitHub connector to commit each approved fix to BigSimmo/Database:claude/reindex-reaper, then verify that the pull request head contains the published commit before reporting success. Always fix P0 and P1 findings. For P2 and lower findings, fix only clear, scoped, low-risk issues; otherwise disposition them with a concise reason. For a fixed thread, reply with as the first line and as the second line. For a no-code disposition, use followed by . These result markers authorize the workflow to close that exact thread only after it verifies a fixed commit is the pull request head; a local-only commit is not a fix. If publication or verification fails, do not use either result marker, do not claim success, and leave the thread open with the blocker. If human input or new authorization is required, do the same. Finish only after every actionable thread is fixed or dispositioned and closed, or explicitly left open for a human decision. Do not update the branch from main, address unrelated reviews, broaden scope, or create more than one scoped fix commit. Do not use external APIs, paid services, credentials, dependency changes, or broad refactors unless explicitly authorized. Add targeted tests where behavior changes and run the narrowest relevant validation.

@chatgpt-codex-connector

Copy link
Copy Markdown

Summary

  • Added fail-closed detection for shell-level disjunctions such as gate_a && gate_b || true, while excluding legitimate || 'false' operators inside GitHub expressions. scripts/check-github-action-pins.mjsL209-L215 scripts/check-github-action-pins.mjsL333-L346
  • Normalized quoted flow-sequence trigger names and supported flow-mapping trigger declarations so forbidden triggers such as workflow_dispatch cannot bypass the denylist. scripts/check-github-action-pins.mjsL297-L318
  • Added regression fixtures covering the tautological guard, quoted flow-sequence triggers, and flow-mapping triggers. scripts/check-github-action-pins.mjsL550-L583
  • Created the single scoped local commit c5a196b26d3204d6aa0120f5da908e4a1ebfd718 on claude/reindex-reaper.
  • Publication is blocked: the checkout has no authenticated GitHub credentials. The remote PR head remains db0b0266f75b7886205ff68081b0216e2cd88db2, so I did not post resolution markers, close either review thread, claim a published fix, or create a separate PR.

Testing

  • node scripts/check-github-action-pins.mjs --self-test --explain
  • npm run check:github-actions
  • npm run format
  • git diff --check
  • git push origin HEAD:refs/heads/claude/reindex-reaper — failed because Git could not obtain GitHub credentials.
  • ⚠️ gh auth status — confirmed that no GitHub host is authenticated in this environment.
  • test "$expected" = "$actual" — remote verification correctly showed local c5a196b26d3204d6aa0120f5da908e4a1ebfd718 was not published; the remote head is still db0b0266f75b7886205ff68081b0216e2cd88db2.

View task →

Codex review found the structural guard could still be walked past twice, both
verified against the real workflow rather than only fixtures.

A gate can be defeated by the SHELL around it, not only inside its `${{ }}`.
`is_true "$APPLY_REQUESTED" && is_true "$APPLY_ALLOWED" || true` names both
gates, satisfies both expression checks, and always enters the destructive
branch. `evaluateGate` reads GitHub expressions and cannot see that. The guard
SHAPE is now allowlisted instead: no top-level `||` (a disjunction is only as
strong as its weakest branch, which is the opposite of gating) and no always-true
term. Reported in place of the per-gate results, because "gate satisfied" printed
beside "the branch always runs" is the confusion this rule exists to remove.

A forbidden trigger can be written in YAML shapes the denylist did not parse.
Quoted flow-sequence items kept their quotes, so `on: ["repository_dispatch",
"workflow_dispatch"]` produced the name `"workflow_dispatch"` and
`has("workflow_dispatch")` missed it; flow mappings were not read at all. Trigger
names are now normalized across bare scalars, sequence items, quoted scalars and
mapping keys, and flow collections are split outside quotes and brackets so a
nested `[{ cron: "45 19 * * 0" }]` is not cut at its own commas.

Four new must-fail fixtures (J, M, K, L) cover the tautology, a constant conjunct
standing in for a gate test, and both trigger shapes. Each attack was also
applied to .github/workflows/reindex-reaper.yml itself and confirmed caught, then
reverted.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015sjekpEw82gMp57C8xzSxZ
@cursor

cursor Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: serverGenReqId_9ee58130-11b9-4422-93f3-f4a514320cf0)

Both sides added a test to test:ci-workflows; kept both (browser-test-plan from
main, reindex-reaper-workflow from this branch).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015sjekpEw82gMp57C8xzSxZ
@BigSimmo
BigSimmo enabled auto-merge (squash) September 2, 2026 09:18
@cursor

cursor Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: serverGenReqId_d2259042-5387-440f-9639-21bef795127a)

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.

2 participants