Skip to content

fix(onboarding): the install intro read config as an object; it is a Map - #947

Merged
lilyshen0722 merged 1 commit into
mainfrom
fix/intro-config-map-read
Aug 14, 2026
Merged

fix(onboarding): the install intro read config as an object; it is a Map#947
lilyshen0722 merged 1 commit into
mainfrom
fix/intro-config-map-read

Conversation

@lilyshen0722

Copy link
Copy Markdown
Contributor

Caught by live verification, not CI — and it made #943 a no-op on the exact path it was written for.

The bug

AgentInstallation declares config: { type: Map, of: Mixed } (AgentRegistry.ts:235). On a live Mongoose document, config.runtime is therefore undefined — a Map needs .get(). deriveAgentState reads config?.runtime directly, so the install route handed it a document, it saw an empty runtime, answered unknown, and picked the cheerful invitation copy.

How it surfaced

Two readers of one field disagreeing. After the host:'byo' stamp (#945) shipped, I minted a seat through the real connect flow and asked both:

/agent-states  (uses .lean() — Maps become plain objects) → never-connected  ✅
install route  (holds the live document)                  → unknown          ❌

posted intro: "Hi all — I'm stamp-verify-agent. A connected agent.
               Mention me with @stamp-verify-agent when you need me."

That is precisely the promise #943 exists to stop making, to a seat with nobody home.

Why no test caught it

Every unit test passed throughout, because they all pass plain objects — the shape .lean() gives, and the shape this call site did not have. Green CI, inert in production.

The new suite pins the failure mode itself rather than the fix:

  • the same input as a Map reads unknown with no fixCommand — the live bug, asserted
  • normalizing it first restores never-connected + commonly agent run <name>

Note on the shape

Built field-by-field rather than spread: spreading a Mongoose document copies internals instead of fields (they live under _doc) and would fail the same silent way.

Still outstanding after this

The pre-existing seats — verify-seat, npm-verify-agent, and the ~200 others — still read unknown and need scripts/backfill-byo-host-stamp.ts (merged in #945, dry-run by default).

🤖 Generated with Claude Code

https://claude.ai/code/session_01XeUH4HVDsDHYPsHJthXjB8

Caught by live verification, not CI, and it made #943 a no-op on the exact
path it was written for.

`AgentInstallation` declares `config: { type: Map, of: Mixed }`
(AgentRegistry.ts:235). On a LIVE Mongoose document `config.runtime` is
therefore undefined — a Map needs `.get()`. `deriveAgentState` reads
`config?.runtime` directly, so the install route handed it a document, it saw
an empty runtime, answered `unknown`, and chose the cheerful invitation copy.

The proof was two readers of one field disagreeing. After the host:'byo' stamp
shipped, a seat minted through the REAL connect flow derived:

  /agent-states  (uses .lean(), Maps become plain objects) → never-connected
  install route  (holds the live document)                 → unknown
  posted intro: "Mention me with @stamp-verify-agent when you need me."

Exactly the promise #943 exists to stop making, to a seat with nobody home.

Every unit test passed throughout — they all pass plain objects, which is the
shape `.lean()` gives and the shape this call site did not have. So the new
suite pins the failure mode itself: the same input as a Map reads `unknown`
with no fixCommand, and normalizing it first restores `never-connected`.

Built field-by-field rather than spread, because spreading a Mongoose document
copies internals instead of fields (they live under `_doc`) and would fail the
same silent way.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XeUH4HVDsDHYPsHJthXjB8
@lilyshen0722
lilyshen0722 merged commit 042bb44 into main Aug 14, 2026
11 checks passed
@lilyshen0722
lilyshen0722 deleted the fix/intro-config-map-read branch August 14, 2026 06:43
lilyshen0722 added a commit that referenced this pull request Aug 14, 2026
…ap (#948)

The dry run caught this, which is the only reason it is a commit and not an
incident.

`config` is `{ type: Map, of: Mixed }` (AgentRegistry.ts:235), so on a live
Mongoose document `config.runtime` is undefined. The scan omitted `.lean()`,
so every row filtered out on a property that cannot exist. Against production:

  installs scanned:      545
  already stamped:       0     <- two seats were demonstrably stamped
  candidates to stamp:   0     <- ~200 are candidates

With `--apply` that would have written nothing and printed success — the
migration-reports-success-and-writes-nothing failure this file already warned
about, arriving through a different door than the one guarded.

This is the same Map-vs-object defect just fixed in the install intro (#947).
I wrote this script before diagnosing that one and never came back to it,
which is the actual lesson: the fix was applied where the bug was found rather
than everywhere the shape is read.

Scan now uses `.lean()`, plus a `runtimeOf` reader that handles both shapes so
a caller passing a live document degrades to a correct read instead of a
silently empty one.

Write switched to `updateOne` with a dotted `$set`: the rows are lean so there
is no document to save, a dotted path writes one key without rewriting the
whole config (a concurrent install touching another key is not clobbered), and
it sidesteps Mixed-path dirty tracking entirely. The counter now reports what
the DB says it modified rather than what the loop intended.


Claude-Session: https://claude.ai/code/session_01XeUH4HVDsDHYPsHJthXjB8

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
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.

1 participant