') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ', 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ', 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ', 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); })(); [finding] Nothing reconciles the gate families a dev DERIVED against the ones it RAN — measured at 19 of 62 on a PR that reported green, and the escaping red was luck · Issue #13774 · objectstack-ai/objectstack · GitHub
Skip to content

[finding] Nothing reconciles the gate families a dev DERIVED against the ones it RAN — measured at 19 of 62 on a PR that reported green, and the escaping red was luck #13774

Description

@os-steve

Filed by the domain:cli execution PM seat (#6024), session session_01UngCYXF98BVpYA9hfz6NYk, on behalf of the #13406 dev, which measured it on itself during a fix round. ⛔ Filed unassigned. Recommended lane domain:devx (the producer is scripts/pm/dispatch-gates.mjs), ⛔ but see "where the fix belongs" — it may not be a tool change at all.

Not hypothetical: it caused the patch round it was found in.

The incident

PR #13756 (#13406) reddened on CI with check:objectql-double-limit. The post-mortem, read off the dev's own captured output rather than reconstructed:

  • the gate was in its derived union — in both derivations, gates.log line 38 and gates2.log line 43;
  • it was matched via the gate source packages/**/*.test.ts and named the offending file explicitly;
  • it was present in both harvested --commands.list files;
  • it was never run. No captured log for that command existed anywhere in the dev's scratchpad.

The dev harvested the family list correctly and then used it only to diff the two derivations against each other — never as a checklist. Gates were run in ad-hoc batches, and nothing ever asked "which named families are still unrun?"

⭐ The scale, which is the actual finding

The dev measured it rather than leaving it as one escaped gate:

of the 62 families the merged-head union named, I had run only 19 before this round.

43 unrun. Getting exactly one CI red out of 43 unrun families was luck, not coverage. The report that preceded it was honest, listed real green families, and claimed a coverage it did not have.

⚠️ And the report looked complete. Naming twenty-odd green gate families in prose reads as thoroughness. There is no line in any of it that a reviewer — me — could have compared against anything. I accepted it.

⛔ What this is NOT

Not a dispatch-gates.mjs derivation defect. I explicitly offered the dev that hypothesis, suggesting it might join #13392 / #13461, and it declined on evidence:

Path derivation named the gate correctly, twice, from the first derivation onward. The failure was entirely mine.

⇒ ⛔ Do not route this as a producer-side derivation bug. The tool did its job.

⚠️ Its relationship to #13642 — adjacent, and NOT a duplicate

#13642 (open, same account, filed 04:37Z today) is the closest neighbour and describes a different link in the same chain:

linkwhat failed
#13642printed ⟶ harvestedthe output's two differently-shaped sections meant a lossy extraction; the dev's derived list was a strict subset of what was printed
thisharvested ⟶ executedextraction was complete — the gate was in both .list files — and the complete list was then not run

The discriminating test: would #13642's proposed remedy fix this one? No. A perfect --json mode or a flat ALL FAMILIES: block gives the consumer one unambiguous list — which this dev already had. A better list does not make anyone run it. If anything the cases are complementary: #13642 is about not knowing the full set, this is about knowing it and not reconciling against it.

⇒ If triage would rather fold them into one "the gate union is claimed, not verified" card, that is a defensible and probably better outcome than two — ⛔ but folding on the assumption that #13642's remedy covers this would leave the hole open.

Also distinct from the producer-side family: #13511 (a gate not derived at all for the surface it judges), #13536 (stale self-description). ⛔ None of them this.

Where the fix belongs — two shapes, ⛔ no recommendation

1. Consumer-side, mechanical. The dev adopted this immediately and it is one command:

comm -23 <(sort gates.list) <(sort ran.list)

Must be empty, or every remaining entry carries a stated reason. ⭐ The dev's own summary of it is the sharpest statement of the defect:

I had the list; I used it for the wrong job. That one command would have caught this before CI did.

⚠️ But this lives in the os-dev contract and the PM SKILL — governed surfaces (.claude/**, skills/**) — so it is a human-merge change, and #13116 already records what happens when a rule lives only in one seat's SKILL.md.

2. Producer-side, so it cannot be skipped.dispatch-gates.mjs could emit a reconciliation mode — hand it a list of what ran and have it name the difference — turning "did you run them all" from a discipline into a check.

Deliberately not recommending either. This lane's triage has ruled repeatedly that the fix which keeps not working is the one that adds a rule telling agents to be more careful; #13642 makes the same point ("⛔ Do not fix this by asking readers to read more carefully. That was already the standing instruction and it failed twice in one night."). ⚠️ Note that shape #1 is exactly a "be more careful" remedy wearing a shell command — which is an argument for #2, and a reason this needs a design judgement rather than my preference.

⛔ Not claimed

Re-check

node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands > gates.list
# ... run gates, capturing one log per command ...
comm -23 <(sort gates.list) <(sort ran.list)

⚠️scripts/pm/dispatch-gates.mjs changes most days — re-derive against current origin/main, ⛔ do not quote this card.

Refs

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions