fix(firmware): populate bundled bootloader hash table#112
Merged
Conversation
Bundled releases.json shipped with hashes.bootloader={}. When the remote
manifest fetch fails (offline, CI outage, DNS), the merged manifest has
no bootloader-hash -> version entries. For devices in firmware mode that
don't report features.bootloaderVersion, resolution falls back to the
hash table; with an empty table, effectiveBlVersion stays undefined and
needsBootloaderUpdate collapses to false. The OOB wizard then skips the
bootloader step and installs firmware on the old bootloader, surfacing
as 'unknown bootloader' on next boot.
Mirror the remote table (v1.0.0 through v2.1.4) into the bundle so
bootloader detection works offline and in degraded-network conditions.
3 tasks
BitHighlander
added a commit
that referenced
this pull request
Jul 16, 2026
…t through (#365) * feat(mcp): make /mcp a dumb pipe — serve the BEX catalog, pass content through The vault hardcoded the MCP tool catalog and text-wrapped every tool result. Two consequences: each new BEX tool needed a cross-repo change (tools/list never mentioned it, tools/call rejected it at the TOOL_NAMES guard), and bex_screenshot was impossible — MCP returns images as {type:'image'} content blocks and the vault could only emit text. The nine browser-driving tools are merged and shipping in keepkey-client (#112/#113) and are inert until this lands. - tools/list proxies to bex_list_tools; TOOLS becomes FALLBACK_TOOLS, served only when the bridge is down so `claude mcp add` still succeeds and the agent can reach bex_status to find out why. - tools/call passes an MCP content-block result through untouched; a plain result is still text-wrapped as before. - Drop the TOOL_NAMES guard. The vault can no longer know the valid names and doesn't need to: the BEX's unknown_tool surfaces as an isError result via the existing catch. One rejection path, in the process that owns the catalog. Auth is untouched: /mcp stays bearer-authed, loopback-only, browser-excluded, which matters more now that bex_click can drive a logged-in tab. The BEX-side Agent-mode toggle (default off) remains the gate. Tests drive a fake BEX socket, so the bridge-up paths (proxied catalog, image passthrough, unknown_tool) are covered with no extension and no device. The four new assertions fail against the unpatched handler. Implements HANDOFF_vault_mcp_dumb_pipe.md (keepkey-client#114). * fix(mcp): validate the tool-name shape, and keep unknown_tool a protocol error Review follow-ups on the dumb-pipe change. 1. Dropping the TOOL_NAMES guard also dropped the only check that `name` was a usable string. The BEX drops frames with a non-truthy `tool` WITHOUT replying (mcpBridge.ts `if (!msg?.id || !msg?.tool) return`), so a missing/empty name was forwarded and then sat for the full 30s CALL_TIMEOUT_MS — where it used to fail instantly with -32602. Validate the shape before forwarding: the vault owns the request shape, the BEX still owns which names are real. 2. The MCP tools spec treats an unknown tool as a PROTOCOL error (-32602); isError is for a valid tool that failed while executing. Map the BEX's unknown_tool back onto rpcError instead of dressing it up as a successful result. The BEX still decides — we only translate its verdict into the envelope the spec asks for. No hardcoded catalog comes back. The fake BEX in the tests now mirrors the real one's silent-drop behavior, so the bridge-up regression test genuinely reproduces the hang: with the guard removed it times out instead of asserting. * chore(mcp): drop the unused post() test helper Dead since before this PR: post() builds a Request and returns it, but nothing calls it — every test goes through call(), which does the same construction and then actually invokes the handler. tsc flagged it TS6133.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fixes the likely root cause of the "unknown bootloader" ticket: the OOB wizard installing firmware on a device whose bootloader needs updating first.
firmware-bundle/releases.jsonshipped withhashes.bootloader: {}. When the remote manifest fetch at startup fails (offline, upstream outage, DNS), the merged manifest has no bootloader hash → version entries. For devices in firmware mode wherefeatures.bootloaderVersionis absent, resolution falls back to the hash table — with an empty table,effectiveBlVersionstaysundefinedandneedsBootloaderUpdatecollapses tofalse. The OOB wizard then skips the bootloader step and installs firmware on top of the old bootloader, which surfaces as "unknown bootloader" on next boot.This PR mirrors the remote table (v1.0.0 → v2.1.4) into the bundle so bootloader detection works offline and in degraded-network conditions.
Test plan
needsBootloaderUpdate=trueand routes to bootloader step (not firmware).[Engine] Loaded bundled manifestlog showslatest=v7.10.0 beta=v7.14.0as before.