Repository navigation
Fresh install: capabilities never get scanned (flaky) #709
Replies: 1 comment 2 replies
|
Thank you for this — it's an exceptionally good report, and you were right to be suspicious that it wasn't three unrelated bugs. We traced all of it through the code. Your analysis holds, and the three symptoms collapse into one root cause: the controller's Also — to get this out of the way — this is our bug, not an artifact of your environment. The root cause is traceable to our own PRD, so the uncertainty you expressed about whether your analysis generalizes is misplaced. It generalizes to every deployment. Details below. What we confirmed(a) The payload is one level deeper than the controller looks. The REST layer wraps tool output as ⇒ (b) (c) One correction to your framing, in our favour: the inner flag does cleanly separate the two states. An absent collection returns inner Why this produces exactly what you saw"Empty forever, but green": Re-scan storm: the list always reads empty, so the diff puts every cluster resource type into Your point 1 also stands independently — Why CI never caught it — and why this is on usOur e2e mock MCP server hand-writes the response as And those assumptions came from PRD #34's API reference, which documented Two things your analysis didn't cover1. Fixing the parse arms a deletion path that has never run. This is the one we'd most want you to know before touching the code. Because That path is unguarded, and The same reasoning applies to the MCP side: any list the controller can't prove is complete must not drive deletions. So a completeness guard needs to land in the same change as the matching fix, not after it. Concretely: scan-when-incomplete is safe (scans are idempotent), delete-when-incomplete is not. 2. Scans are fire-and-forget, so "trigger accepted" is all you can learn. We think this is the actual reason it's flaky rather than deterministic. There's a Your three questions1. Do we want autonomous periodic resync upstream? Yes. Startup-only wasn't a deliberate design choice you're missing — and your instinct to point at 2. Should a failed trigger be surfaced and retried? Yes — and the fix is checking 3. Is the 100-item cap intentional? We don't think so — it reads as a display guard rather than an API limit. It sits next to On side effects of raising it: cheaper than you'd expect, because the expensive part already happens unconditionally. Two refinements on your Fix 3, though:
One more, which you inferred correctlyYour remark that "the MCP server's readiness check doesn't actually verify the capability subsystem" is right, and worse than stated. Local embeddings make it worse: This is the root of "flaky" rather than "broken." Retries and resync intervals are compensation for a Ready signal that doesn't mean ready. We're adding a What we've done and where you come inTwo PRDs, one per repo:
They're sequenced so neither blocks the other: with the truncation guard, the controller degrades safely against a server that still caps at 100. Both of these are yours if you want them. You found this, you already have the patches, and it'd be good to have your name on the fix. I tried to assign both issues to you directly, but GitHub won't let me assign someone without repo access — happy to sort that out if you'd like them formally assigned, just say so. To be clear though, this is an offer and not a handoff. If you'd rather I took either or both, just say so and I'll pick them up, no explanation needed. Same if you want the controller one but not the MCP-server one, or you'd rather do the parsing fix and leave the resync to me. You've already contributed a lot here, and finding the bug doesn't obligate you to fix it. Two asks if you do take the controller work:
Either way, the analysis gets credited in the PRD and changelog. And if you can share what your fork's patches settled on for the resync default and the diff guard, we'll fold that in regardless of who writes the code. Thanks again — this was a genuinely hard one to spot from the outside, and the fact that every status surface lied made it harder. |
Uh oh!
There was an error while loading. Please reload this page.
Hello @vfarcic . Thank you for your quick and constructive response on the last discussion. It encourages me to keep engaged. I followed up with an PR and starting this discussion which describes another issue we faced in our setup.
TL;DR
On a fresh
dot-ai-stackinstall, we repeatedly ended up with an empty capabilities collection — the capability scan just never populated it.Digging in, it wasn't one bug but a small pile of them that line up badly on a fresh cluster:
lastError, which looks identical to a healthy, fully-scanned system.I'm opening this as a discussion because we've hit all three in our downstream fork and patched them. However I'm not sure which of these issues "hold true" for other environments or whether my analysis is even fully reasonable. Note this spans two repos — most of it is controller-side (
dot-ai-controller), plus one change in the MCP server (dot-ai).Initial observation
Fresh install of the stack, wait for everything to go green, then ask
recommendfor something — and it behaves as if the cluster has no capabilities at all. Checking the vector store directly, thecapabilitiescollection was empty (0 documents), even though the cluster had a few hundred resource types that should have been scanned.The
CapabilityScanConfigresource showed up normally. There was no error nor a a previous event recorded on it, the controller pod was happily running with zero restarts, and the MCP server answered normally. So from every status surface we had, the system claimed to be fine — it just hadn't actually scanned anything.The one reliable "fix" we found early was to restart the controller. After a restart the scan ran, the collection filled up, and
recommendstarted working. That was the clue that the scan was tied to startup rather than to an ongoing "keep things in sync" loop.Deep dive 1: how is the scan supposed to happen, and where does it fall down?
The scan is driven by a the
dot-ai-controller. The design — laid out in the PRDs linked below — is roughly: on startup, list the cluster's resource types, list what's already in the capabilities index, diff them, and scan whatever's missing (a full scan when the index is empty, which is exactly the fresh-install case).The trouble is that this diff-and-scan is bound to startup and to the config resource being (re)created. On a fresh install the config gets created once, which fires exactly one scan attempt. If the vector backend / embedding subsystem isn't functionally ready at that precise moment — and it often isn't, because the MCP server's readiness check doesn't actually verify the capability subsystem — that single attempt fails. There's no periodic "try again" loop, so the system just sits there empty until something external (a controller restart, a config change) happens to trigger another scan.
Deep dive 2: why didn't we see an error?
When the scan trigger failed because the backend wasn't ready, the failure wasn't surfaced. The backend returns an HTTP 200 with a "success: false" body (reachable but not functional) — and the controller interpreted that as "list returned 0 capabilities," not as "this call failed." So it happily concluded there was nothing wrong.
Deep dive 3: the re-scan storm (the opposite failure)
Root cause there was under-counting: the controller was reading far fewer capabilities than were actually stored — partly a response-shape parsing mismatch (an extra nesting level, plus matching on the wrong field), and partly a server-side list endpoint that silently capped results at 100. Either way the diff always thought a big chunk was "missing" and kept re-scanning. So the same subsystem can fail in two opposite directions — scan nothing, or scan everything endlessly — and both trace back to the controller not having an accurate picture of what's already there.
Related issues
Linking these for context:
dot-ai #216 – PRD: Controller-Based Autonomous Capability Scanning (closed/completed). The umbrella PRD. Its own description says the controller should "check for existing capabilities on startup, trigger full scan if needed".
dot-ai-controller #34 – PRD: Autonomous Capability Scanning Controller (closed/completed). Phase 2 of PRD: Controller-Based Autonomous Capability Scanning #216, the actual controller implementation. Its commit history explicitly notes "Remove InitialScanComplete status check (always reconcile on startup)" and "Full scan only when MCP is empty (fresh install)".
dot-ai-controller #28 – Resource Visibility and Status Tracking (closed/completed). The sibling controller for resource sync already ships "initial sync & periodic resync … on startup and hourly" with a configurable
resyncIntervalMinutes.dot-ai #51 – Capability Scanning Performance Optimization (closed, superseded by PRD: Controller-Based Autonomous Capability Scanning #216). Relevant to the re-scan storm we experienced.
Downstream fixes
Fix 1 — autonomous periodic resync (controller)
Add a
resyncIntervalMinutesfield toCapabilityScanConfig(default 60) and, on the "config unchanged" reconcile path, actually re-run the diff-and-scan on that interval instead of just idling. This makes the initial scan self-healing: if the first attempt fails because the backend wasn't ready, a later resync picks it up — no controller restart or CR recreation required. This deliberately mirrors what the ResourceSync controller in #28 already does.Fix 2 — surface MCP backend failures instead of masking them (controller)
Treat an operation-level failure (HTTP 200 but "success: false", e.g. vector backend not ready) as a real, retryable error rather than as "0 capabilities." That way a failed scan is retried and shows up as an error instead of silently looking like a healthy, fully-scanned system.
Fix 3 — stop under-counting existing capabilities (controller + MCP server)
Two parts that together stop the re-scan storm:
Controller: parse the actual (nested) shape of the MCP list response and match capabilities by their
resourceNamerather than by the internal UUIDid, so the diff compares like with like.MCP server (
dot-ai): raise the capabilitieslistcap from 100 to other limits (10000), since the controller needs the full list to compute an accurate diff. (This is the one change that lives in thedot-airepo rather than the controller.)Pro: the controller finally sees the true count, so the diff converges to zero and the storm stops.
What I'd like to discuss
listendpoint intentional? If so, what would be the side effect of raising the limit?All reactions