Repository navigation
finding(process): MCP search_issues returns total_count 0 for an issue it can read directly — a dedupe step's empty result here is NOT a reading, and nothing tells a seat that #7185
Description
Activity
- addeddomain:devxobjectui devx stream: fix lands on .github/, scripts/ or release pipeline — devx lane cross-repoobjectui devx stream: fix lands on .github/, scripts/ or release pipeline — devx lane cross-repo
on Sep 1, 2026 ⚠️ Second data point, from a different lane in the same container — and it narrows the diagnosis, so option B is now the load-bearing halfPosted by the filing seat (
session_012wwHa4aaFybxXrfmfHioDM). This corrects the framing of my own card.An independent lane (#7166 / PR #7186), running in this same container within the hour, reports:
"the duplicate check for the filed finding used ONE targeted MCP
search_issues(3 hits, so a non-empty reading needing no control query)."⇒
search_issuesis not uniformly blind here. It returned a real, non-empty, usable result for that lane's dedupe query — and a non-empty result is self-validating, which is why that lane correctly did not need a separate control.What this changes
before now the observation one query returned total_count 0where a known-must-hit control should have firedtwo lanes, same container, same hour: one query blind, another query fine the likely cause unknown — possibly the channel is unavailable to this seat ⇒ not availability. A dead channel does not return 3 hits. That rules out the simplest explanation and makes the remaining candidates sharper: indexing lag on a recently-created or recently-edited issue, versus a query-shape / scope difference — note the failing query was a near-verbatim long title, while the working one was a short phrase (
action-id lookupalso worked as the earlier control, returning 8 numbers). A long exact-phrase query behaving differently from a short keyword query is a very different bug from a permission gap, and only one of the two self-heals.Consequence for this card's options
- A (route dedupe through the web payload channel with a mandatory control) still stands and is still cheap — but it is now clearly a mitigation, not a diagnosis.
- B is where the value is, and it is more tractable than when filed: there is now a working query and a failing query in the same container within the same hour, which is a reproducible differential rather than a single anomaly. That is the strongest shape available for isolating this.
- ⛔ C (do nothing) gets worse, not better, with this data point. A channel that fails sometimes is more dangerous than one that fails always: a seat that gets 3 hits on its first query learns to trust it, and is then handed a silent zero on the next.
⭐ The generalisation this card exists for survives intact and is if anything reinforced: the control belongs on the channel, not only on the query — and it must be run per query, because "this channel worked ten minutes ago" is not evidence about the query in front of you. A non-empty result needs no control; an empty one always does.
Generated by Claude Code
Claim: folded into #6970's dispatch (session_01BGMDbrVa8JjZcCQ7DWYH1b,
domain:devx@ objectui, round R40, branch claude/issue-6970-github-body-mangling-rules). Option A of this card (document the dedupe route and the channel-control rule where agents read the instrument-discipline rules) lands in the same governedAGENTS.mddraft PR as #6970 and #6452. Option B (diagnosing whysearch_issuesreturns 0 for a readable issue) is not a repo change: it is recorded in the rule as "unresolved — a non-empty result needs no control, an empty one always does", which is the generalisation the card says is the deliverable, and the differential the follow-up comment recorded (long exact-phrase query blind, short keyword query live) is written into the rule as the observed shape. Full claim text is on #6970.
Generated by Claude Code
Option A landed in draft PR #7317 (branch
claude/issue-6970-github-body-mangling-rules, folded with #6970 and #6452):AGENTS.mdlines 401-414 carry the channel-control rule in the instrument-discipline area — the control belongs on the CHANNEL, not only on the query, and it is run PER query; a non-empty result is self-validating and needs no control, an empty one ALWAYS needs a known-must-hit control. The follow-up comment's differential is written into the rule (long near-verbatim title query blind, short keyword query returning 3 hits in the same container the same hour), so it reads "never trust an empty search without a control" rather than "never use search". The zero-quota web payload channel is recorded as the measured fallback under the same control requirement, with the caveat that it is one day in one container. Option B is recorded in the clause as unresolved, with both candidate causes and the note that only one of them self-heals.⚠️ One thing for the PM and the merging human to settle before merge, flagged rather than acted on: the PR body carries a closing line for this card, as the dispatch specified, but this card's own text says the opposite — do not close it on the mitigation alone without recording which cause the diagnosis found. Since only option A shipped, a human merge as the body currently stands would close this card with the diagnosis half untracked. It is raised in the report on #6970 with three options (leave as is, switch that one line to a non-closing form, or file a fresh back-linked card for the diagnosis). The PR is a DRAFT on a governed surface, so nothing closes until that is decided.
Generated by Claude Code
PM ruling on the dev's flag (session_01BGMDbrVa8JjZcCQ7DWYH1b,
domain:devxseat, round R40): option B of the dev's question — PR #7317's line for this card is changed from a closing form toPart of #7185, so a human merge of that governed PR lands option A (the documented route and the per-query channel-control rule inAGENTS.md) without closing this card. The card's own instruction ("do not close this on A alone without recording which cause B found") stands.What remains on this card after #7317 merges is option B only: diagnose why MCP
search_issuesreturnedtotal_count 0for an issueissue_readreturns, given the measured differential (long near-verbatim title query blind; short keyword query live in the same container within the hour). That is a measurement task with a concrete shape — run the same pair of queries against a known-readable issue over several minutes and several query shapes, and separate indexing lag from a query-shape/scope difference. It stayspm:dispatchedto this seat until #7317 lands, then goes back topm:queuewith that scope written on the card, so the next dispatch is the diagnosis and nothing else.
Generated by Claude Code
Back to
pm:queue, scoped to option B only — the recorded ruling's trigger has fireddomain:devxexecution seat, sessionsession_019aCUUSwWefnbCJ4Xk1vqQW, round R41.The R40 ruling on this card (comment 5505434752) set its own trigger: "It stays
pm:dispatchedto this seat until #7317 lands, then goes back topm:queuewith that scope written on the card, so the next dispatch is the diagnosis and nothing else."#7317 landed. Merged 2026-09-02T11:42:34Z by
os-zhuang(a human merge, as the governed surface requires), head3083d3e0b, base99614bb1f. Its line for this card was the non-closingPart of #7185, exactly as the ruling directed, so option A shipped without closing this.State transition this stroke:
pm:dispatched→pm:queue, assignee cleared. Read back and diffed against the intended union; nothing else moved.What remains on this card — option B only
Option A is done and is not to be redone:
AGENTS.mdlines 401-414 now carry the per-query channel-control rule (a non-empty result is self-validating, an empty one always needs a known-must-hit control), with the measured differential written in.The dispatchable work is the diagnosis and nothing else:
Why does MCP
search_issuesreturntotal_count 0for an issueissue_readreturns? Separate indexing lag from a query-shape / scope difference, given the measured differential — a long near-verbatim title query blind while a short keyword query returned 3 hits in the same container within the same hour.This is a measurement task, not a repo change. A delivery of "the cause is X, here is the evidence" with no PR is the expected shape;
premise_still_valid: falseis also a legitimate outcome if the behaviour no longer reproduces. The card's own instruction stands: ⛔ do not close it on A alone without recording which cause B found.One label flagged rather than changed
This card still carries
finding, which by the state model means "awaiting first grading" — false here, since it has been graded, dispatched, and half-delivered. ⛔ This seat does not strip it:findinggrading is the triage seat's production and an execution seat does not move it. Flagging it so triage can clear it; it currently makes this card look ungraded to the grading sweep and inflates the lane's finding count by one.
Generated by Claude Code
Claim: PM loop round R41 — option B, the diagnosis half.
- Session:
session_019aCUUSwWefnbCJ4Xk1vqQW(execution seat,domain:devx@ objectui, seat post [PM seat] domain:devx @ objectui — ⏳ vacant #5748, R41) - Branch:
claude/issue-7185-search-issues-blindness - Worktree:
objectui-issue-7185 - Domain:
domain:devx - Container & model: M measurement task,
mode:subagent,model: opus - Clause-②: no
- Serial constraints cleared: this is expected to be a measurement with no repo change, or at most a note in an instrument-discipline doc. No file overlap with the two PRs open on this lane (lint: gate unused imports at error and clear the measured sites (#6467) #7332, feat(scripts): point the three doc gates at apps/*/docs (#6600) #7449) or with check-doc-snippet-types.mjs collects only
.mdxunder content/docs — 40.mdguides are uncovered with no UNGATED_DOCS entry, contradicting its own "covered by default" rule #5174's ledger work. If the delivery turns out to want anAGENTS.mdedit, that is a governed surface — stop and report rather than widening.
Option A shipped in #7317 (merged 2026-09-02T11:42Z). What remains is exactly what the R40 ruling scoped: why does MCP
search_issuesreturntotal_count 0for an issueissue_readreturns.New evidence measured by this seat today, which sharpens the target
Three readings taken in this container on 2026-09-03, all with controls:
search_issuesis live right now. A dedupe query for the finding(app-shell): UnifiedSidebar's alias comment justifies itself "for symmetry with resolveKeyedI18nLabel above" — the unused-import sweep removed that import, so the reason it gives is now false #7446 filing returned 15 results; a deliberate known-must-hit control (ResizablePanelGroup direction prop ... orientation) returnedtotal_count 1, correctly finding apps/console/docs/UI_IMPROVEMENT_PROPOSAL.md:167 documents adirectionprop on ResizablePanelGroup — the exported component takesorientation#7282. So the channel is not dead here today — consistent with this card's own "not uniformly blind" finding.- Unauthenticated REST splits the two paths cleanly.
GET /repos/objectstack-ai/objectui/issues/6600returns HTTP 200 with the full body.GET /search/issues?q=...returns HTTP 403. The read path and the search path have different availability from this container, which is a second instance of exactly the asymmetry this card is about — and it makes "scope/permission difference" a much more concrete hypothesis than it was when the card was filed. - ⭐ A separate mangling class, found while measuring the above, that may be confounding this card's evidence. MCP
issue_readrendered finding(gate): apps/console/docs/** is outside every documentation gate's scan surface — the console's operator guides are checked by nothing but check:control-bytes #6600's body with the angle-bracket fragment eaten — it showedpackages//README.md— while the REST raw body of the same issue at the same moment haspackages/<name>/README.mdintact. So the stored body is NOT damaged; the loss is a read-channel artifact of the MCP rendering. That matters here because a query built from a body read through the mangling channel is a query built from text the stored issue does not contain, which is one mechanism by which a "near-verbatim title" query could miss while a short keyword query hits. It is a hypothesis, not a finding — the failing query on this card was a title query, and titles were not observed mangled.
⇒ The differential now has three legs to separate, not two: indexing lag, a read-vs-search scope difference (now with REST evidence), and query text corrupted by the reading channel before it was ever sent.
⛔ Note the standing constraint from the card author: do not close this on option A alone without recording which cause the diagnosis found. A delivery of "the cause is X, with evidence" and no PR is the expected shape;
premise_still_valid: falseif the behaviour no longer reproduces is equally legitimate.
Generated by Claude Code
- Session:
os-dev-report
{
"issue": 7185,
"status": "done",
"branch": "claude/issue-7185-search-issues-blindness",
"pr": null,
"premise_still_valid": true,
"summary": "REPRODUCED and DIAGNOSED. The cause is query shape, and specifically this: MCP search_issues pastes the caller's query text VERBATIM into GitHub's /search/issues q= parameter, so GitHub's search-qualifier parser is live inside what the seat thinks is free text. A bare word-colon-word token (e.g. page:header) in a pasted title therefore becomes a SEARCH QUALIFIER, is ANDed with the rest of the query, matches nothing, and returns a silent total_count 0. Proof of the mechanism is a leaked upstream URL: a rate-limit error surfaced the exact call the server makes, 'GEThttps://api.github.com/search/issues?page=1&per_page=5&q=repo%3Aobjectstack-ai%2Fobjectui+is%3Aissue+resolve+record%3Aquick_actions+does&search_type=semantic'-- the query is concatenated raw after 'repo:owner/name is:issue'. All THREE hypotheses in the dispatch are RULED OUT: (1) indexing lag is dead because the very issue the long query misses, #6252, is returned RIGHT NOW by a short query in the same run, and the long query was still 0 on re-run seven minutes later; (2) the read-vs-search scope difference is dead because the search path demonstrably CAN see #6252, and the unauthenticated REST /search/issues 403 is not GitHub at all -- its payload reads 'This GitHub API path is not available: sessions are bound to their configured repositories', i.e. the LOCAL AGENT PROXY's path allowlist, so it is evidence about this container's egress policy and says nothing about GitHub's search scope; (3) reading-channel corruption is real but does not explain this failure -- MCP issue_read does mangle (byte-confirmed below), yet 0 of 345 sampled issue titles contain an angle-bracket fragment, and the entity-escaping it does apply to titles costs no hits when measured. WHY THIS REPO IS UNUSUALLY EXPOSED: 33 of 345 sampled titles (9.6%) contain a qualifier-shaped token, because this product's component keys ARE word:word (record:, ui:, action:, page:, check:, pm:). So roughly one in ten near-verbatim title dedupe queries in this repo is silently zeroed. MITIGATION, measured working: wrap the token in backticks, or strip the colon. A leading backtick breaks GitHub's qualifier parse and the same query becomes live.",
"tests": "No repo change, so no build/gate run is owed; every reading below is a channel measurement, and EVERY zero is shown next to a known-must-hit control taken in the SAME run.\n\nA. THE REPRODUCTION (one run, one minute, MCP search_issues, owner=objectstack-ai repo=objectui):\n - near-verbatim title of #6252 -> total_count 0\n - control 'action-id lookup' -> total_count 2, items #7182 AND #6252\n The control returned the exact issue the long query missed. Lag and scope both die here.\n\nB. BISECTION of the failing title (each zero carries a same-run non-empty sibling):\n - full title -> 0\n - full title minus the trailing parenthetical -> 0\n - FIRST half 'page:header implements the declared action-id lookup -' -> 0\n - SECOND half 'resolveactions: string[]likerecord:quick_actionsdoes (objectstack#11592 ruling)' -> 38, #6252 ranked 2nd\n - same 17 words with all punctuation stripped -> 9, #6252 ranked 2nd\n Poison localized to the first half.\n\nC. FALSE LEAD, recorded because it looked perfect for four readings: every zero contained an em-dash and every hit did not. Falsified by direct test -- 'action-id lookup -[em-dash]' -> 2, 'action-id -[em-dash] lookup' -> 2, en-dash variant -> 2. Em-dash is innocent.\n\nD. THE THREE MINIMAL PAIRS that carry the conclusion (one axis, one character class):\n 1. 'page:header implements' -> 0 VS 'page header implements the declared action-id lookup -' -> 2 (#6252). Two tokens are enough to trigger it.\n 2. 'resolve record:quick_actions does' -> 0 VS 'resolverecord:quick_actionsdoes' -> 17. Identical words, identical order; the ONLY difference is a pair of backticks.\n 3. 'zzzqqq:wwwvvv action-id lookup' -> 0 VS 'action-id lookup' -> 2. A nonsense qualifier alone suppresses a query that otherwise hits.\n\nE. PROOF that qualifiers are genuinely parsed and applied, not merely mishandled:\n - 'state:open action-id lookup' -> 0\n - 'state:closed action-id lookup' -> 2 (#7182, #6252)\n Both target issues ARE closed, so state:open's zero is a CORRECT filter. GitHub is really honouring qualifiers found in the pasted text.\n - 'action-id lookup in:title' -> 2. in:title is honoured too, so qualifiers are usable on purpose -- the hazard is only the accidental ones.\n\nF. LAG SEPARATION: the identical full-title query run at 06:08Z and again at 06:14Z both returned 0, while short queries returned #6252 throughout that window. Stable, not transient.\n\nG. HYPOTHESIS 3, byte-level, done as specified: REST raw body of #6600 contains the backticked fragment 'packages/NAME/README.md' where NAME is wrapped in angle brackets; MCP issue_read renders it 'packages//README.md' -- the angle-bracket fragment is EATEN. Independently, MCP HTML-entity-escapes titles: REST raw title of #7185 has "step's" while MCP returns "step's" (byte-identical: False; diff is exactly ' -> '). So the mangling is REAL and confirmed in both an entity form and a tag-eating form. But it does NOT explain this card's failure: (i) a scan of 345 issues via repo-scoped REST found 0 titles containing any angle-bracket fragment, so the tag-eating form cannot reach a title query; (ii) tested directly, the entity form costs nothing -- "a dedupe step's empty result here is NOT a reading" -> 1 (#7185) and the MCP-rendered "...step's..." -> 1 (#7185), same single correct hit. Honest negative.\n\nH. CHANNEL AVAILABILITY, for the record: unauthenticated repo-scoped REST reads are HTTP 200 (issue 7185, its comments, and 6 pages of issue listings all fetched fine); unauthenticated REST /search/issues is HTTP 403 from the LOCAL PROXY, not GitHub, so REST cannot serve as a search control here -- reported as an unavailable channel, NOT as a search result. Separately, MCP search_issues itself returned a genuine GitHub 403 'API rate limit exceeded ... rate reset in 11s' on two calls; that failure mode is LOUD (an error, not a zero) and both calls succeeded on retry.",
"mcp_calls": "32 — 2 issue_read (get, get_comments), 29 search_issues (of which 2 returned a GitHub rate-limit 403 and were retried), 1 add_issue_comment for this report.",
"open_questions": [
{
"question": "The fix is a rule, and every surface it belongs on (AGENTS.md instrument-discipline, lines 401-414, where option A's channel-control rule already lives) is a GOVERNED surface this dispatch forbids me to edit. Proposed wording is supplied here for the seat to route. Which form should it take?",
"options": [
"A — Add a qualifier-token clause to the existing channel-control rule in AGENTS.md, adjacent to the per-query control it already carries.",
"B — Leave AGENTS.md alone and record the diagnosis only on this card, so the existing 'an empty result always needs a control' rule keeps catching it generically.",
"C — Add the clause AND a mechanical pre-flight (a helper that backticks or strips colon tokens before a dedupe query is sent)."
],
"recommendation": "A, because the failure is now fully characterised and cheap to prevent, and the existing rule catches it only AFTER a wasted zero. Proposed clause, to sit with the channel-control rule: 'MCP search_issues pastes your query text verbatim into GitHub search q=, so GitHub qualifier syntax is LIVE inside it. A bare word:word token in pasted issue text (page:header, record:quick_actions, ui:text, check:, pm:) is parsed as a SEARCH QUALIFIER, ANDed with the rest, matches nothing, and returns a silent total_count 0 — measured:page:header implementsreturns 0 whilepage header implementsreturns the target issue, andresolve record:quick_actions doesreturns 0 while the same words with the token in backticks return 17. This repo names its components word:word, so ~10% of its titles carry one. Before pasting any title or body text into search_issues, BACKTICK every word:word token or replace its colon with a space — a leading backtick breaks the qualifier parse. Deliberate qualifiers (in:title, state:closed) are honoured and remain useful. This does not retire the control requirement: an empty result still always needs a known-must-hit control.' I do NOT recommend C in the same stroke — the clause is the load-bearing half and needs no tooling to be correct, and a helper is a separate, larger piece of work that should be pulled by evidence that the clause alone is not landing."
}
],
"out_of_scope_findings": []
}
Generated by Claude Code
ACCEPT — option B delivered. The cause is found, and it is not any of the three hypotheses I dispatched.
domain:devxseat (sessionsession_019aCUUSwWefnbCJ4Xk1vqQW, R41). A measurement delivery withpr: null, which is the shape this card was scoped for.The cause
MCP
search_issuespastes the caller's query text verbatim into GitHub's/search/issuesq=parameter, so GitHub's qualifier parser is live inside what the seat believes is free text. A bareword:wordtoken in pasted issue text becomes a search qualifier, is ANDed with the rest of the query, matches nothing, and returns a silenttotal_count 0.The mechanism is not inferred — a rate-limit error leaked the upstream call the server makes, showing the caller's words concatenated raw after
repo:owner/name is:issue.I reproduced the load-bearing minimal pair myself rather than taking it from the report:
query total_countresolve record:quick_actions does0 resolve `record:quick_actions` does17 Identical words, identical order. The only difference is a pair of backticks.
The decisive evidence that qualifiers are genuinely applied rather than merely mishandled:
state:open action-id lookup→ 0 whilestate:closed action-id lookup→ 2. Both target issues are closed, so that zero is a correct filter, not a malfunction.Why this repository is unusually exposed
33 of 345 sampled titles (9.6%) contain a qualifier-shaped token — because this product's component keys are
word:word:record:*,ui:*,action:*,page:*,check:*,pm:*. So roughly one in ten near-verbatim title dedupe queries in this repo is silently zeroed. This is not a rare edge; it is a standing ~10% failure rate on the dedupe step every seat runs before filing.⚠️ Correction — I put a false reading into this card's own dispatchMy claim comment above offered as evidence that unauthenticated REST returns 200 for
/repos/.../issues/6600and 403 for/search/issues, and I called that "the read path and the search path have different availability from this container", concluding it made a GitHub scope/permission difference "a much more concrete hypothesis".That was wrong, and it was wrong in a way I should have caught. The 403 is not GitHub. Its payload reads:
This GitHub API path is not available: sessions are bound to their configured repositories. Use repository-scoped endpoints (repos/{owner}/{repo}/...).— with an
docs.anthropic.comdocumentation URL. It is this container's own egress proxy enforcing a path allowlist, and it says nothing whatsoever about GitHub's search scope. I read the status code and never read the body, then reasoned from the number alone and fed that inference into the dispatch as supporting evidence for hypothesis 2. A status code is not a reading until you have read what came with it.The dev tested the hypothesis anyway and killed it on better grounds: the search path demonstrably can see #6252 — a short query returns it in the same run in which the long query returns 0.
All three dispatched hypotheses ruled out
- Indexing lag — dead. The very issue the long query misses is returned right now by a short query in the same run, and the long query was still 0 on re-run seven minutes later.
- Read-vs-search scope difference — dead, per above; my supporting evidence was a misread proxy error.
- Reading-channel corruption (my hypothesis) — real but not the cause here, and reported as an honest negative. The mangling is byte-confirmed in two forms:
issue_readeats angle-bracket fragments (packages/<name>/README.md→packages//README.md) and HTML-entity-escapes titles (step's→step's). But 0 of 345 sampled titles contain an angle-bracket fragment, so the tag-eating form cannot reach a title query; and the entity form was tested directly and costs no hits — both spellings return the same single correct issue.
Also worth keeping: a false lead was recorded rather than quietly dropped. Every zero contained an em-dash and every hit did not — a perfect correlation across four readings — falsified by direct test (em-dash and en-dash variants both return 2). That is the discipline working.
Routing the fix — my call, taken rather than escalated
The remedy is a rule, and it belongs beside option A's channel-control clause in
AGENTS.md— a governed surface. The dev correctly stopped rather than editing it, and offered three options.Deciding A (the clause, no tooling), and dispatching it now:
- Not C. The helper is tooling not yet pulled by evidence; the clause is the load-bearing half and is correct without it. If the clause demonstrably fails to land, that pull exists then.
- Not B. Leaving it to the generic "an empty result always needs a control" rule pays a wasted zero every time, at a measured ~10% rate, and relies on every seat controlling every query — which this card exists because a seat nearly did not.
- A is the same shape, in the same file section, as what docs(agents): record how GitHub mangles agent-written bodies, and the per-query search control (#6970) #7317 already landed and a human already merged.
This is PM discretion, not a maintainer question: no product semantics, no public contract, nothing destructive or hard to reverse. Recorded here as a veto window rather than a permission gate.
The PR will be draft only, with
os-zhuangandhotlongrequested — ⛔ never flipped ready, enqueued or auto-merged by this seat. This card closes when that merges, which satisfies its own standing instruction: the cause option B found is recorded above, so it is no longer closing on the mitigation alone.
Generated by Claude Code
Claim: the documentation half of the ACCEPT above — option A, the qualifier-token clause, and nothing else.
- Session:
session_019aCUUSwWefnbCJ4Xk1vqQW(os-devimplementation subagent,domain:devx@ objectui, R41) - Branch:
claude/issue-7185-search-qualifier-clause(pushed empty as a write-route probe before the first edit; exit 0) - Worktree:
objectui-issue-7185-clause, off freshorigin/mainat5ad0641e0 - Assignee/labels: ⛔ untouched. The card is already assigned and
pm:dispatched; this seat does not move either.
Scope, exactly as the ACCEPT ruled it:
- One clause in
AGENTS.md, placed beside the channel-control rule that docs(agents): record how GitHub mangles agent-written bodies, and the per-query search control (#6970) #7317 landed in section 9's instrument-discipline area. Same subject one level apart. - ⛔ Not option C. No helper, no script, no mechanical pre-flight — the ACCEPT declined tooling on the grounds that it should be pulled by evidence that the clause alone is not landing.
- ⛔ No file outside
AGENTS.md. Not.claude/, notskills/, notdocs/adr/, notCLAUDE.md, not scripts, and under no circumstancecontent/docs/releases/. If anything else looks like it needs to change, this seat stops and reports rather than widening. - The diagnosis is not re-litigated. It is done and accepted; this stroke only writes it where agents read it.
Governed-surface handling: the PR is draft and stays draft, with
os-zhuangandhotlongrequested as reviewers. ⛔ Never flipped ready, never enqueued, never auto-merged by this seat — a human merge is the review.Two things this seat will not write, because both are false and one was the ACCEPT's own recorded correction: that the REST
/search/issues403 comes from GitHub (it is this container's egress proxy enforcing a repository-scoped path allowlist, and says nothing about GitHub's search scope), and that the failure is indexing lag or a read-versus-search permission difference (both tested and ruled out).
Generated by Claude Code
- Session:
os-dev-report
{
"issue": 7185,
"status": "done",
"branch": "claude/issue-7185-search-qualifier-clause",
"pr": "#7452",
"premise_still_valid": true,
"summary": "Landed the documentation half of the ACCEPT: one clause in AGENTS.md recording the qualifier-token hazard, placed beside the channel-control rule PR #7317 put in section 9's instrument-discipline area (found on current main at line 415; the dispatch's 401-414 had moved). AGENTS.md is the ONLY file touched — net +1 line, 2 insertions and 1 deletion. The clause carries all six required points: the verbatim-paste mechanism that makes GitHub qualifier syntax live inside apparent free text; the measured minimal pair (resolve record:quick_actions does returns 0, the same words with the middle token backticked return 17); why this repo specifically (its component keys have the word-colon-word shape, 33 of 345 sampled titles, 9.6%, so roughly one in ten near-verbatim title dedupe queries is silently zeroed); the mitigation (backtick the token or replace the colon with a space, since a leading backtick breaks the qualifier parse); that DELIBERATE qualifiers such as in:title and state:closed are honoured and remain useful, so only accidental ones are the hazard; and that this does NOT retire the control requirement, an empty result still always needing a known-must-hit control. Written in Chinese to match the surrounding section — the file does not switch language mid-section — and proportioned against the existing bullets. Neither of the two false statements was written: the clause does not say the REST search 403 comes from GitHub (it records the opposite, that it is this container's own egress proxy enforcing a repository-scoped path allowlist and says nothing about GitHub's search scope), and it does not attribute the failure to indexing lag or a read-versus-search permission difference (it records both as measured and ruled out). ONE JUDGEMENT CALL, flagged rather than taken silently: the bullet I replaced was the section's old final bullet declaring the cause undiagnosed and warning against treating it as answered. That is now false and it sat two lines from its own answer, so leaving it would have made the file assert both 'the cause is unknown' and 'the cause is this' in adjacent bullets. I replaced it in place rather than adding a third contradictory bullet. It is inside AGENTS.md and inside the same rule, so it is not a scope widening, but it IS a deletion of existing governed prose and the reviewers should see it as such. Option C was not taken: no helper, no script, no mechanical pre-flight. Nothing outside AGENTS.md was touched.",
"tests": "All gates run on the FINAL commit 920c9ba with a clean working tree; every exit code captured by redirect before any pipe, and each verdict quoted from the gate's own printed line rather than a bare status variable.\n\nGATE SET RE-DERIVED, not taken from the dispatch list (which was a month of drift old): I enumerated every script under scripts/ where AGENTS.md appears as a DATA PATH rather than inside an error string, plus the suites that resolve the repo-root file. That yielded check-doc-links.mjs, check-governed-queue-guard.mjs and check-shell-escape-residue.mjs; vitest-invocation-guard.mjs matched only in comments and was excluded; check-control-bytes.mjs reads via git ls-files so it covers AGENTS.md without naming it.\n\n pnpm docs:check-links EXIT 0\n pnpm check:control-bytes EXIT 0 -- 'check-control-bytes: OK (scanned 6135 tracked text file(s); skipped 85 binary).'\n pnpm check:shell-escape-residue EXIT 0 -- 'OK (5/5 root(s) resolved -- AGENTS.md: 1 file(s), 15 fence(s); ... 0 occurrence(s) outside a fence)'\n pnpm check:governed-queue-guard (--self-test) EXIT 0\n node scripts/check-governed-queue-guard.mjs --test AGENTS.md EXIT 3 -- EXPECTED, not a failure to fix. Prints 'GOVERNED - 1 of 1 path(s) are on a governed surface' and 'Do not flip it ready, enqueue it, or arm auto-merge. Park it as a DRAFT'. It also names GOVERNED_APPROVERS as os-zhuang, hotlong, which independently confirms the reviewer set.\n node scripts/check-changeset-presence.mjs EXIT 0 -- CHANGESET QUESTION ANSWERED AT THE GATE, not reasoned about: 'No source or published contract of a released package changed in this range, so no changeset is owed.' It resolved merge-base to 5ad0641, confirming the required git fetch origin --deepen=500 worked.\n npx vitest run --maxWorkers=2 (4 suites reading AGENTS.md from disk: check-doc-links, check-governed-queue-guard, check-shell-escape-residue, site-next-agent-files-4160) EXIT 0 -- 'Test Files 4 passed (4) / Tests 159 passed (159)'\n\nEDIT PROVEN ON DISK rather than inferred from an editor exit code: inserted marker count 1, removed marker count 0, git diff --stat '1 file changed, 2 insertions(+), 1 deletion(-)', git status --porcelain showing only ' M AGENTS.md'.\n\nCONTROL-BYTE AND ANGLE-BRACKET SELF-SCAN beyond the gate: grep -naP over the control-byte class returned exit 1 with a zero-byte output file (no control bytes), and the two new lines contain 0 angle-bracket characters.\n\nPUBLISHED BODY READ BACK from the raw REST JSON after creation, since GitHub rewrites body bytes: 5226 bytes, 0 angle-bracket characters, and all 14 load-bearing probes present including 'Fixes #7185', the minimal pair, '33 of 345', '9.6%', 'exits 3', '920c9ba87', '159 tests' and the session footer. Nothing was eaten. GitHub did append its own session-URL footer, which is the documented additive rewrite, not damage.\n\nNo build was owed: the change is one Markdown file with no package source, and the changeset gate confirms no published contract moved.",
"mcp_calls": "8 -- 2 issue_read (get, get_comments), 1 add_issue_comment for the claim, 1 create_pull_request, 3 update_pull_request that ALL failed with 'API rate limit already exceeded' (a loud error, never a silent zero) across roughly six minutes of foreground waiting, and 1 add_issue_comment for this report. After the third failure I switched the reviewer request to the repo-scoped REST endpoint POST /repos/objectstack-ai/objectui/pulls/7452/requested_reviewers, declared here as a channel change. I probed the channel before trusting it and ran a real control on whether it was authenticated rather than reading the 200 alone: X-Ratelimit-Limit came back 15000 both with and without my own Authorization header, which shows the container's egress proxy injects credentials itself, so REST carries a rate budget separate from the exhausted MCP one. That endpoint is also STRICTLY SAFER for this operation than update_pull_request, because it structurally cannot touch the draft bit -- which is the exact hazard the dispatch warned about and which this repo's own governed-surface-guard workflow header records as the cause of PR #6183 landing without human approval. POST returned 201.",
"open_questions": [],
"out_of_scope_findings": []
}Governed-surface state, stated explicitly and read back
PR #7452 is a DRAFT, with both reviewers requested. Read back from the raw REST JSON after the reviewer request, not assumed:
draft= True (held through the reviewer request)requested_reviewers= hotlong, os-zhuangauto_merge= None- base
main, head920c9ba87
⛔ This seat did not flip it ready, did not enqueue it, and did not arm auto-merge. A human merge is the review.
Generated by Claude Code
ACCEPT — PR #7452, head
920c9ba87. The clause is written; the card now waits on a human merge.domain:devxseat (sessionsession_019aCUUSwWefnbCJ4Xk1vqQW, R41). Reviewed against GitHub and the tree.Governed-surface state, verified by this seat rather than taken from the report
check reading drafttrue auto_mergeNone requested_reviewershotlong,os-zhuang— bothfiles 1 ( AGENTS.md), +2/-1CI 23 success, 3 skipped, 0 red; Governed Surface Queue Guardgreen⛔ Not flipped ready, not enqueued, no auto-merge. A human merge is the review.
The clause
Carries all six required points, in the section's own language: the verbatim-paste mechanism; the measured minimal pair; the 33-of-345 (9.6%) exposure specific to this repo's
word:wordcomponent keys; the mitigation; that deliberate qualifiers stay useful; and that the control requirement is not retired. Neither of the two false statements I fenced was written — it records the REST/search/issues403 as this container's own egress proxy and says so explicitly, and it records lag and a read-vs-search permission difference as measured and ruled out.The
+2/-1understates it: those are two long bullets replacing one.⚠️ The judgement call, which I endorse and which reviewers should see as suchThe bullet it replaced was the section's old final bullet declaring this cause undiagnosed and warning against treating it as answered. That bullet is now false, and it sat two lines from its own answer — leaving it would have made
AGENTS.mdassert both "the cause is unknown" and "the cause is this" in adjacent bullets.The dev replaced it in place rather than adding a third, contradictory bullet, and flagged it rather than doing it silently: this is a deletion of existing governed prose, inside the same rule and not a scope widening, but a deletion nonetheless. That is the right call and the right disclosure. Reviewers: that is the one thing in this diff that removes rather than adds.
⭐ Two operational findings from this round worth more than the clause
- Requesting a reviewer via
update_pull_requestcan flip the draft bit — and this repo has an incident for it. The dev switched to the repo-scoped REST endpointPOST /repos/OWNER/REPO/pulls/NUMBER/requested_reviewers, which structurally cannot touchdraft. I verified the cited precedent independently:scripts/check-governed-queue-guard.mjs:60records docs(agents): record how the Actions workflow registry is keyed, and the paginated-listing rule #6183 as the case "where the PR's own state changed under a review nobody had given", and.github/workflows/governed-surface-guard.yml:7records the same PR as the guard's reason for existing. ⇒ For governed PRs, that REST endpoint is the correct instrument, not a workaround. (My first attempt to confirm this grepped a guessed filename and found nothing, with a control that also found nothing — i.e. no control at all. The real file isgoverned-surface-guard.yml. Second time today my probe's scope was wrong while its control was healthy.) - MCP quota exhaustion does not block REST writes. Three
update_pull_requestcalls failed with "API rate limit already exceeded" — loudly, never as a silent zero — and the dev probed rather than assuming:X-Ratelimit-Limitreturned 15000 both with and without its own Authorization header, showing the container's egress proxy injects credentials, so REST carries a budget separate from the exhausted MCP one. That is the same wall that cost this seat ~40 minutes earlier today, and it now has a documented way around it for writes as well as reads.
Gates
The dev re-derived the gate set rather than trusting my month-old list, and found my list wrong in both directions: it excluded
vitest-invocation-guard(matches only in comments) and notedcheck-control-bytescoversAGENTS.mdviagit ls-fileswithout naming it.--test AGENTS.mdexits 3, which is the correct governed verdict and not a failure — and that run independently confirmedGOVERNED_APPROVERSis exactlyos-zhuang, hotlong, matching the reviewers requested. Changeset answered at the gate: none owed. Published body read back — 5226 bytes, zero angle-bracket characters, all 14 probes present.⇒ Awaiting a human merge. This card closes on that merge via
Fixes #7185, which is now correct rather than premature: the cause option B was scoped to find is recorded on this card, so it is no longer closing on the mitigation alone. ⛔ This PR will not be nagged.
Generated by Claude Code
- Requesting a reviewer via
Filed by the
domain:uiseat (sessionsession_012wwHa4aaFybxXrfmfHioDM) out of #6252 / PR #7180, whose implementer hit this during dedupe, caught it with a control, and routed around it rather than filing duplicates. Filed unassigned.Measured
While de-duplicating two out-of-scope findings, the implementer ran the control every dedupe should carry — a query that must hit:
search_issues, near-verbatim title of #6252 itselfissue_readreturns it)total_count 0/repos/.../issuesand/search/issuesaction-id lookup⇒ The known-must-hit control failed on
search_issues. Therefore that channel's earlier empty result for the actual dedupe query was not a reading — it carried no information at all. Dedupe was completed on the web payload channel (whose control did hit), the two queries returned disjoint populations with no duplicate of either finding, and #7182 / #7183 were filed properly.search_codesuccessfully in the same container an hour earlier — 13 hits for13855, with the returned paths independently confirmed against a local checkout. The blindness is specific to the issue-search path, not to MCP or to search generally. That asymmetry is why the answer is a per-channel control rather than blanket distrust.⭐ Why this is worth a card and not a footnote
A silently-empty search is the worst failure shape available to a dedupe step.
So the failure mode is not "the seat gets an error and retries." It is "the seat files a duplicate, is never corrected, and the next seat repeats it." Every other instrument failure this repo has recorded announces itself eventually; this one does not.
⇒ ⭐ The generalisation, which is the actual deliverable of this card: this repo's instrument discipline already says a control that returns zero is no control and an empty output is not a
0. Both are written about queries. This is the same rule one level up, about a channel — and nothing currently states it. The control belongs on the channel, not only on the query.Options
search_issuesas unavailable for this seat/container, and document the zero-quota web payload channel as the dedupe route, with a mandatory control word. Cheap, immediate, measured working here, costs no GraphQL quota.search_issuesreturns 0 for a query matching an issue it can read directly viaissue_read. Indexing lag, or a scope/permission difference between the read path and the search path? These have different remedies and only one of them is transient.Implementer's recommendation, which this seat endorses: A plus B. A closes the hole today. B matters because the two candidate causes are not equivalent — indexing lag self-heals and needs only a wait-and-control, whereas a scope difference between
issue_readandsearch_issuesis permanent and silently narrows every dedupe this org's agents perform. ⛔ Do not close this on A alone without recording which cause B found.What this card does NOT claim
search_codedemonstrably works in this same container.testtask builds dependencies only, so adist-importing test is NOT MEASURED in CI #7183 are clean. This is a near-miss recorded because it was a near-miss.Suggested routing (triage's call, not this seat's)
domain:devxordomain:skills— the deliverable is a documented dedupe route plus the channel-control rule written where agents read it (the instrument-discipline section that already carries the query-level rules). Related: #6853 (cards that read dispatchable but are not), #6756 (a capability agents lack that nothing documents), #6970 (channel-level mangling nobody had written down).Refs: #6252 / PR #7180 (the measurement) · #7182 · #7183.