Summary
The screenshot-table gate auto-closed contributor PRs that made no visual change, because whenPaths included apps/loopover-ui/public/** — a directory that is overwhelmingly non-visual.
Mitigated in the ORB private config for JSONbored/loopover. Filing to track (a) verifying the sibling repos and (b) deciding whether the matcher should support exclusions.
Root cause
apps/loopover-ui/public/ contains openapi.json (generated, 536KB), robots.txt, sitemap.xml, sw.js, manifest.webmanifest, .nojekyll, plus favicons.
CLAUDE.md requires contributors to run npm run ui:openapi on any API/schema change, which regenerates apps/loopover-ui/public/openapi.json. Following the documented contribution steps therefore tripped a gate demanding Desktop/Tablet/Mobile × Dark before/after screenshots of a JSON spec file. With action: close and the gate being one-shot, those PRs were auto-closed with no recovery path.
Note src/settings/agent-actions.ts observes that screenshot_table is "our single top close reason all-time" — consistent with systemic over-firing.
Confirmed damage
Of the last 40 closed loopover PRs, 11 touched openapi.json with zero real visual changes; 5 were auto-closed by this gate: #9414, #9411, #9410, #9409, #9405. All five were reopened and re-gated after the fix.
Fix applied (ORB private config, jsonbored__loopover/.loopover.yml)
whenPaths narrowed to the actually-rendered UI:
whenPaths:
- "apps/loopover-ui/src/components/**"
- "apps/loopover-ui/src/routes/**"
- "apps/loopover-ui/src/styles.css"
Dropped apps/loopover-ui/public/** and apps/loopover-ui/components.json (shadcn tooling config, not a rendered change).
Why the glob had to be narrowed rather than excluded
matchesAny (packages/loopover-engine/src/signals/change-guardrail.ts) is positive-only: no ! negation, and {a,b} braces are regex-escaped to literals. There is no way to express "public/** except openapi.json" in the current matcher.
Follow-up
- Check the sibling repos —
JSONbored/metagraphed and JSONbored/awesome-claude private configs may carry the same over-broad globs. Not yet verified.
- Consider whether
matchesAny should support exclusion patterns, so a gate can say "this directory except these generated artifacts" without enumerating every visual extension.
- Consider whether a gate whose
action: close is one-shot should require a positive visual signal (an image diff in the changed files) rather than a path prefix alone.
Summary
The screenshot-table gate auto-closed contributor PRs that made no visual change, because
whenPathsincludedapps/loopover-ui/public/**— a directory that is overwhelmingly non-visual.Mitigated in the ORB private config for
JSONbored/loopover. Filing to track (a) verifying the sibling repos and (b) deciding whether the matcher should support exclusions.Root cause
apps/loopover-ui/public/containsopenapi.json(generated, 536KB),robots.txt,sitemap.xml,sw.js,manifest.webmanifest,.nojekyll, plus favicons.CLAUDE.mdrequires contributors to runnpm run ui:openapion any API/schema change, which regeneratesapps/loopover-ui/public/openapi.json. Following the documented contribution steps therefore tripped a gate demanding Desktop/Tablet/Mobile × Dark before/after screenshots of a JSON spec file. Withaction: closeand the gate being one-shot, those PRs were auto-closed with no recovery path.Note
src/settings/agent-actions.tsobserves thatscreenshot_tableis "our single top close reason all-time" — consistent with systemic over-firing.Confirmed damage
Of the last 40 closed loopover PRs, 11 touched
openapi.jsonwith zero real visual changes; 5 were auto-closed by this gate: #9414, #9411, #9410, #9409, #9405. All five were reopened and re-gated after the fix.Fix applied (ORB private config,
jsonbored__loopover/.loopover.yml)whenPathsnarrowed to the actually-rendered UI:Dropped
apps/loopover-ui/public/**andapps/loopover-ui/components.json(shadcn tooling config, not a rendered change).Why the glob had to be narrowed rather than excluded
matchesAny(packages/loopover-engine/src/signals/change-guardrail.ts) is positive-only: no!negation, and{a,b}braces are regex-escaped to literals. There is no way to express "public/** except openapi.json" in the current matcher.Follow-up
JSONbored/metagraphedandJSONbored/awesome-claudeprivate configs may carry the same over-broad globs. Not yet verified.matchesAnyshould support exclusion patterns, so a gate can say "this directory except these generated artifacts" without enumerating every visual extension.action: closeis one-shot should require a positive visual signal (an image diff in the changed files) rather than a path prefix alone.