#1662 · CLI/server payload drift: bb plugin install <path> gets HTTP 422 'expected {source}' while direct POST /api/v1/plugins/install succeeds
TL;DR
Plain-language framing. bb plugin install <path> does not talk to the server by hand: it goes through the shared @bb/sdk client, which builds the JSON body for POST /api/v1/plugins/install. The server validates that body with a zod schema marked .strict(), which means any key the server does not know is a hard 422.
Commit fc3454809 (#1579, 2026-08-14, first shipped in bb-app 0.38.0, published 2026-08-15 00:44 UTC) added a second key, selection, to that body. The new SDK sends selection: {"kind":"root"} on every install, even a plain root install where the server would default to root anyway. Every server built before that commit (bb-app ≤ 0.37.x, or any dev checkout older than 08-14) still validates with the strict { source }-only schema, so it answers exactly what the reporter saw: HTTP 422: expected { "source": string }. A hand-written curl -d '{"source": ...}' has no selection key and therefore succeeds against the very same server.
So this is not a bug in one commit's own CLI/server pair (with matched versions both the CLI and the curl succeed, verified below), but a forward-compatibility break between a ≥0.38.0 CLI/SDK and a ≤0.37.x server. It bites in ordinary setups: npx bb-app@latest bb … or a source checkout's pnpm bb … talking to a still-running older desktop app on :38886. The issue was filed 22 h after 0.38.0 was published; the error text quoted matches the pre-0.38.0 route byte for byte. I reproduced it with the real released artifacts (bb-app@0.38.0 CLI → bb-app@0.37.0 server) and with a unit test against the SDK at 16ceb3a54.
Deeper issue: the SDK fills a server-side default on the client (contrary to AGENTS.md “fill defaults once at the server boundary”), and there is no CLI↔server version check at all; even after omitting selection, the ≥0.38.0 CLI installs the plugin on a 0.37.0 server but then fails parsing the response (new required publisherLabel field), i.e. mixed versions only ever work by accident.
Claims vs findings
| Claim | Status | Evidence |
|---|---|---|
bb plugin install /path/to/plugin fails with HTTP 422 “expected {source}” | Verified (version skew) | Reproduced verbatim with released artifacts: bb-app@0.38.0 CLI against a bb-app@0.37.0 server prints Error: HTTP 422: expected { "source": string } (repro step 3). With matched versions (0.37.0↔0.37.0, 16ceb3a54↔16ceb3a54) the same command succeeds (steps 2 and 5). |
Direct curl … -d '{"source": "/path/to/plugin"}' succeeds on the same server | Verified | Step 4: {"ok":true,"plugin":{"id":"issue-1662-fixture",…}} from the 0.37.0 server; adding "selection":{"kind":"root"} to the same curl body yields the 422. |
| “The CLI and the server appear to disagree about the install payload shape” | Verified, but only across versions | At any single commit CLI/SDK and server share pluginInstallSourceRequestSchema from @bb/server-contract. The disagreement is between the ≥fc3454809 SDK (always sends selection) and the <fc3454809 server (strict { source }). |
| “Likely a drift introduced on one side without updating the other” | Refuted as stated | fc3454809 updated contract, server, SDK and CLI in one commit. The drift is a wire-compat break with previously shipped servers, not a half-applied change. |
| “Observed on a current build” | Unverifiable / partially refuted | Reporter gave no versions. On a current build with a current server the command works (step 5). The quoted error string only exists in servers before fc3454809 (the post-commit message lists selection), so the server the reporter hit was pre-0.38.0 (or a pre-08-14 dev checkout); the CLI was ≥0.38.0. |
Reporter's curl uses "source": "/path/to/plugin" (no path: prefix) | Fine | parsePluginSource treats a bare path like path: (apps/server/test/services/plugins/plugin-install.test.ts:292); the CLI itself sends path:<abs>. |
Environment
- Worktree
/home/sawyer/projects/bb/.claude/worktrees/wf_242c3e11-a10-12at16ceb3a54(detached), Linux 7.0.0-29-generic x86_64, node v24.18.0 (dev launcher used the repo's node 22 for the server), pnpm 10, turbo 2.8.3. - “New” server: my dev instance from this worktree — app
:17700, server:25700, host daemon:33700, data dir/home/sawyer/.bb-dev/projects-bb-.claude-worktrees-wf_242c3e11-a10-12-090cc0ace365.GET /api/v1/system/version→{"currentVersion":"0.0.0-dev",…}. - “Old” server: the released npm package
bb-app@0.37.0run asnpx --yes -p bb-app@0.37.0 bb-server --data-dir /tmp/bb-1662-old-data --server-port 25737(its own throw-away data dir;GET /api/v1/system/version→{"currentVersion":"0.37.0",…}). Log: bb-server-0.37.0.log. - CLIs:
npx --yes -p bb-app@0.37.0 bb,npx --yes -p bb-app@0.38.0 bb, and this worktree'snode packages/scripts/dist/commands/run-cli.js(built at 16ceb3a54). - Fixture plugin:
/tmp/bb-1662-plugin= package.json + server.ts. - Pitfall when reproducing from inside a bb thread: the host daemon exports
BB_CLI=…/host-daemon/dist/bband every officialbbentrypoint re-execs that binary (apps/cli/src/bb-cli-reexec.ts).unset BB_CLIfirst, otherwise “npx -p bb-app@0.37.0 bb” silently runs the daemon's (newer) CLI. My first 0.37.0-CLI run did exactly that and mis-reported a 422; the transcripts below are after unsetting it.
Minimal reproduction
Fewest steps: one old server, one new CLI, one curl. Nothing below touches ~/.bb.
- Create the fixture plugin and start a 0.37.0 server on a scratch data dir (in a second terminal, leave it running):
$ mkdir -p /tmp/bb-1662-plugin $ cat > /tmp/bb-1662-plugin/package.json <<'EOF' { "name": "issue-1662-fixture", "version": "0.1.0", "bb": { "name": "Issue 1662 fixture", "description": "Minimal plugin used to reproduce #1662.", "branding": { "icon": "Zap" }, "server": "./server.ts" } } EOF $ echo 'export default function plugin(bb: any) { bb.log.info("issue-1662 fixture loaded"); }' > /tmp/bb-1662-plugin/server.ts $ unset BB_CLI $ npx --yes -p bb-app@0.37.0 bb-server --data-dir /tmp/bb-1662-old-data --server-port 25737 [07:18:05] INFO: [server] Server listening {"bindHost":"127.0.0.1","port":25737,"dataDir":"/tmp/bb-1662-old-data"} $ curl -s http://localhost:25737/api/v1/system/version {"currentVersion":"0.37.0","latestVersion":"0.38.0","source":"npm","updateAvailable":true,"isDevelopment":false,"upgradeCommand":"npx bb-app@latest"} - Control — the matching 0.37.0 CLI installs fine:
$ unset BB_CLI; BB_SERVER_URL=http://localhost:25737 npx --yes -p bb-app@0.37.0 bb plugin install /tmp/bb-1662-plugin --yes Installing issue-1662-fixture@0.1.0 from /tmp/bb-1662-plugin Plugins are full-trust code running inside the BB server. They can read all local BB data, including other plugins' secrets. Installed: issue-1662-fixture@0.1.0 running source: path:/tmp/bb-1662-plugin exit=0 $ curl -s -X DELETE http://localhost:25737/api/v1/plugins/issue-1662-fixture {"ok":true} - The bug — the 0.38.0 CLI (first release containing fc3454809) against the same 0.37.0 server:
$ unset BB_CLI; BB_SERVER_URL=http://localhost:25737 npx --yes -p bb-app@0.38.0 bb plugin install /tmp/bb-1662-plugin --yes Installing issue-1662-fixture@0.1.0 from /tmp/bb-1662-plugin Plugins are full-trust code running inside the BB server. They can read all local BB data, including other plugins' secrets. Error: HTTP 422: expected { "source": string } exit=1Expected:Installed: issue-1662-fixture@0.1.0 running. Actual: HTTP 422 with the exact text the issue quotes. Nothing was installed (server log has noplugin issue-1662-fixtureline for this attempt). - The reporter's curl, and the same curl with the key the new CLI adds:
$ curl -s -w '\nHTTP %{http_code}\n' -X POST http://localhost:25737/api/v1/plugins/install \ -H 'Content-Type: application/json' -d '{"source":"path:/tmp/bb-1662-plugin"}' {"ok":true,"plugin":{"id":"issue-1662-fixture","source":"path:/tmp/bb-1662-plugin","rootDir":"/tmp/bb-1662-plugin","version":"0.1.0","provenance":"direct",…"status":"running",…}} HTTP 200 $ curl -s -w '\nHTTP %{http_code}\n' -X POST http://localhost:25737/api/v1/plugins/install \ -H 'Content-Type: application/json' -d '{"source":"path:/tmp/bb-1662-plugin","selection":{"kind":"root"}}' {"ok":false,"error":"expected { \"source\": string }"} HTTP 422That second body is exactly what the ≥0.38.0 CLI sends. Captured with a 20-line HTTP sink (capture-server.mjs, log capture-server.log): the worktree CLI at 16ceb3a54 and the 0.38.0 CLI both POSTPOST /api/v1/plugins/install {"source":"path:/tmp/bb-1662-plugin","selection":{"kind":"root"}}while the 0.37.0 CLI postsPOST /api/v1/plugins/install {"source":"path:/tmp/bb-1662-plugin"} - Control — matched current versions do not fail (this is why the bug is invisible in the repo's own tests): dev instance from this worktree at 16ceb3a54, CLI from the same worktree, and also the old 0.37.0 CLI against the new server (the new schema defaults
selection, so the old body is accepted — the break is one-directional):$ scripts/bb-dev-app current # server :25700 $ unset BB_CLI; BB_SERVER_URL=http://localhost:25700 node packages/scripts/dist/commands/run-cli.js plugin install /tmp/bb-1662-plugin --yes Installing issue-1662-fixture@0.1.0 from /tmp/bb-1662-plugin Plugins are full-trust code running inside the BB server. They can read all local BB data, including other plugins' secrets. Installed: issue-1662-fixture@0.1.0 running source: path:/tmp/bb-1662-plugin exit=0 $ curl -s -X DELETE http://localhost:25700/api/v1/plugins/issue-1662-fixture {"ok":true} $ BB_SERVER_URL=http://localhost:25700 npx --yes -p bb-app@0.37.0 bb plugin install /tmp/bb-1662-plugin --yes … Installed: issue-1662-fixture@0.1.0 running source: path:/tmp/bb-1662-plugin exit=0
Unit-level repro (fails on 16ceb3a54)
File: 1662/repro/issue-1662-install-body.test.ts; also placed at packages/sdk/test/issue-1662-install-body.test.ts in my worktree. It captures the body the SDK really sends for a plain plugins.install({ source }) and parses it with the pre-0.38.0 route schema (copied verbatim from packages/server-contract/src/api/plugins.ts@1c3f3eff0, the parent of fc3454809). Run: cd packages/sdk && pnpm exec vitest run test/issue-1662-install-body.test.ts.
/**
* Repro for get-bb/bb#1662.
*
* `bb plugin install <path>` builds its request through
* `sdk.plugins.install`. Since fc3454809 (bb-app 0.38.0) the SDK always
* sends `selection: { kind: "root" }` even when the caller asked for nothing
* but a plain root install. Every server released before that commit
* (bb-app <= 0.37.x) validates the install body with a *strict* schema that
* only knows `source`, so the extra key is rejected with
* HTTP 422 `expected { "source": string }` -- while a hand-written
* `curl -d '{"source": ...}'` succeeds against the very same server.
*
* The first test FAILS on 16ceb3a54 (and on 0.38.0): it captures the body the
* SDK really sends and parses it with the pre-0.38.0 route schema (copied
* verbatim from packages/server-contract/src/api/plugins.ts at
* 1c3f3eff0, the parent of fc3454809).
*/
import { describe, expect, it } from "vitest";
import { z } from "zod";
import { createBbSdk } from "../src/core.js";
import { createHttpTransport } from "../src/transport-http.js";
import type { FetchImplementation } from "../src/response.js";
// packages/server-contract/src/api/plugins.ts @ 1c3f3eff0 (bb-app 0.37.x):
const legacyPluginInstallRequestSchema = z
.object({ source: z.string().min(1) })
.strict();
async function captureInstallBody(
args: Parameters<
ReturnType<typeof createBbSdk>["plugins"]["install"]
>[0],
): Promise<unknown> {
let captured: unknown;
const fetch: FetchImplementation = async (_input, init) => {
captured = JSON.parse(String(init?.body));
// Mimic a bb-app 0.37.x server: strict `{ source }` only.
const ok = legacyPluginInstallRequestSchema.safeParse(captured).success;
return new Response(
JSON.stringify(
ok
? { ok: true, plugin: {} }
: { ok: false, error: 'expected { "source": string }' },
),
{ status: ok ? 200 : 422, headers: { "content-type": "application/json" } },
);
};
const sdk = createBbSdk({
transport: createHttpTransport({
baseUrl: "http://bb.test",
fetch,
runtime: "node",
}),
});
// The response is a stub, so the SDK's response parsing may throw; only the
// request body matters here.
await sdk.plugins.install(args).catch(() => undefined);
return captured;
}
describe("issue #1662: plugin install request body vs pre-0.38.0 servers", () => {
it("a plain root install sends only { source } (accepted by <=0.37.x servers)", async () => {
const body = await captureInstallBody({ source: "path:/tmp/my-plugin" });
// FAILS on 16ceb3a54: body is
// {"source":"path:/tmp/my-plugin","selection":{"kind":"root"}}
// and the strict legacy schema reports: Unrecognized key(s) in object: 'selection'
expect(legacyPluginInstallRequestSchema.safeParse(body)).toMatchObject({
success: true,
});
});
it("a subdirectory install still sends an explicit selection", async () => {
const body = await captureInstallBody({
source: "git:github.com/acme/plugins",
subdirectory: "packages/notes",
});
expect(body).toEqual({
source: "git:github.com/acme/plugins",
selection: { kind: "subdirectory", path: "packages/notes" },
});
});
});
Result on 16ceb3a54 (the first assertion fails because the body carries selection; the second passes):
$ cd packages/sdk && pnpm exec vitest run test/issue-1662-install-body.test.ts
❯ @bb/sdk test/issue-1662-install-body.test.ts (2 tests | 1 failed) 24ms
× a plain root install sends only { source } (accepted by <=0.37.x servers) 23ms
FAIL issue #1662: … › a plain root install sends only { source } (accepted by <=0.37.x servers)
AssertionError: expected { success: false, …(1) } to match object { success: true }
- Expected
+ Received
{
- "success": true,
+ "success": false,
}
❯ test/issue-1662-install-body.test.ts:67:62
Test Files 1 failed (1)
Tests 1 failed | 1 passed (2)
Root cause
1. The SDK fills a server default on the client and puts it on the wire. packages/sdk/src/areas/plugins.ts#L238-L244 maps the CLI's --subdirectory/--plugin to a selection and, when neither is given, returns { kind: "root" }; #L421-L437 then parses { source, selection } through pluginInstallSourceRequestSchema (whose .default(ROOT_PLUGIN_SOURCE_SELECTION) would fill it anyway) and posts the parsed object:
function pluginSourceSelection(args: PluginInstallArgs): PluginSourceSelection {
if (args.subdirectory !== undefined) {
return { kind: "subdirectory", path: args.subdirectory };
}
if (args.plugin !== undefined) return { kind: "entry", name: args.plugin };
return { kind: "root" }; // <-- always on the wire
}
…
const body = pluginInstallSourceRequestSchema.parse({
source: input.source,
selection: pluginSourceSelection(input),
});
const response = await requestParsed("/api/v1/plugins/install", pluginInstallResponseSchema, jsonInit("POST", body));
The CLI (apps/cli/src/commands/plugin.ts#L1123-L1131) only passes subdirectory/plugin when the flags were given, so for bb plugin install <path> the SDK is the one adding selection.
2. The install route is strict on both sides of the change. Current contract packages/server-contract/src/api/plugins.ts#L243-L251:
export const pluginInstallSourceRequestSchema = z
.object({
source: z.string().min(1),
selection: pluginSourceSelectionSchema.default(ROOT_PLUGIN_SOURCE_SELECTION),
})
.strict();
Its predecessor, shipped in every bb-app ≤ 0.37.x (packages/server-contract/src/api/plugins.ts@1c3f3eff0#L206-L208, i.e. the tree of desktop-v0.37.0):
export const pluginInstallSourceRequestSchema = z
.object({ source: z.string().min(1) })
.strict();
and the route that consumes it (apps/server/src/routes/plugins.ts#L409-L425, unchanged in structure; the old build's message at @1c3f3eff0#L420 was 'expected { "source": string }'):
app.post("/plugins/install", async (context) => {
…
const parsed = pluginInstallRequestSchema.safeParse(json);
if (!parsed.success) {
return context.json({ ok: false, error: 'expected { "source": string, "selection"?: … }' }, 422);
}
Why the symptom follows. zod .strict() rejects unknown keys. A ≥fc3454809 SDK sends selection; a <fc3454809 server's strict schema has no such key → safeParse fails → the route answers 422 with its canned message. The reporter's curl body has only source → passes the old schema → install succeeds. Old CLI → new server works because the new schema gives selection a default. So the compatibility matrix is exactly what the reporter observed:
| CLI / SDK | server ≤ 0.37.x (strict {source}) | server ≥ 0.38.0 / 16ceb3a54 |
|---|---|---|
≤ 0.37.x (sends {source}) | OK (step 2) | OK (step 5) |
≥ 0.38.0 / 16ceb3a54 (sends {source, selection}) | HTTP 422 “expected { "source": string }” (step 3) | OK (step 5) |
curl -d '{"source":…}' | OK (step 4) | OK |
How a user ends up in the red cell. The CLI and the server are separately launched processes: npx bb-app@latest bb plugin install … (or npx --package bb-app bb …) resolves the newest CLI while the desktop app / bb-app start from before the upgrade keeps serving :38886; and a source checkout's pnpm bb … (production mode, root package.json:42) targets the packaged app on :38886 by default. The reporter is on a fork verifying plugin behavior (#1661), was filing 22 h after 0.38.0 went to npm, and quoted the pre-0.38.0 error string, which no ≥0.38.0 server can emit.
Underlying issue. Two design gaps make this class of break silent: (a) the SDK materialises a server-side default (selection: root) on the client, which per AGENTS.md (“fill it in once at the server boundary”) belongs to the server; and (b) nothing compares the CLI's version with the server's (GET /api/v1/system/version exists but the CLI never reads it, and no request carries a client version header). Evidence that (b) is real: with the SDK patched to omit selection, the 16ceb3a54 CLI did install the plugin on the 0.37.0 server (it appears in GET /api/v1/plugins) but then failed on the response — publisherLabel (added in 47aa1d357) is required by the new installedPluginSchema. Mixed-version pairs are only ever compatible by luck; the user just gets an opaque zod error instead of “your CLI is 0.38.0 but the server is 0.37.0”.
Proposed fix (first principles)
- Stop sending the default (SDK). Make
pluginSourceSelectionreturnundefinedfor a root install and omit the key from the body; keep sending an explicit selection for--subdirectory/--plugin. The server already defaultsselection, so behavior against ≥0.38.0 servers is unchanged and ≤0.37.x servers accept the request again. Prototype diff (applied, tests run, then reverted in my worktree): proposed-fix-sdk-omit-root-selection.patch. It makes the repro test pass and the SDK suite green (92/92,turbo typecheck --filter=@bb/sdkclean) after updating the one assertion inpackages/sdk/test/sdk.test.ts:1423-1426that locked in the old body. Three app tests assert the same body and need the same one-line change:apps/app/src/hooks/queries/plugin-catalog-queries.test.ts:104,apps/app/src/components/plugin/management/AddPluginDialog.test.tsx:150,180. What could go wrong: nothing on the wire for current servers (default is identical); a nested install (--subdirectory/--plugin) against a ≤0.37.x server still 422s, which is correct — that server cannot do nested installs — but see (2) for making the message honest. - Detect version skew in the CLI (real fix for the class). On startup of any server-talking command, fetch
GET /api/v1/system/version(already returnscurrentVersion) and compare with the CLI's ownresolveBbAppVersion()(apps/cli/src/version.ts). Warn (or fail for mutating commands) with “bb CLI 0.38.0 is talking to bb server 0.37.0 at http://…:38886 — restart/upgrade the app or use its bundled CLI”. Alternatively send anx-bb-client-versionheader and let the server answer 4xx with a clear message; that is a wire change touching every SDK consumer, so the CLI-side check is the smaller first step. Skip the check when either side is0.0.0-dev. - Optional hardening: keep new request schemas
.strict()(good for catching typos), but adopt the rule that clients never send optional/defaulted fields they were not asked to set — that is what makes additive server changes forward-compatible with strict validation.
PR review
No open PR is linked to this issue (also checked gh pr list --search 1662).
Related issues
- #1661 — same reporter, same session (“running this patch on a fork”); unrelated mechanism (cross-realm
Responsecheck), closed 2026-08-17. - #1579 (fc3454809) — introduced
selection; #1628 (47aa1d357) — added the requiredpublisherLabelthat also breaks new-CLI/old-server response parsing. - #1774 — install by name from a git subdirectory (same route/selection contract; a fix here should keep it in mind).
- AGENTS.md “Server And Daemon” already mandates a protocol-version bump for daemon wire changes; there is no equivalent for the CLI/SDK ↔ server HTTP surface, which is the gap this issue exposes.
Appendix
Timeline evidence
$ git log -1 --format='%h %ad %s' --date=iso fc3454809 fc3454809 2026-08-14 10:05:09 -0700 Plugin collection manifests and nested git installs (#1579) $ git tag --contains fc3454809 desktop-v0.38.0 $ git log -1 --format='%h %ad %s' --date=iso desktop-v0.37.0 fe432e3b1 2026-08-12 01:41:14 +0000 Prepare bb-app 0.37.0 $ npm view bb-app time --json | tail -5 "0.38.0": "2026-08-15T00:44:29.936Z", … $ gh issue view 1662 --json createdAt → 2026-08-15T22:26:10Z $ git log 16ceb3a54..origin/main --oneline -- packages/sdk/src/areas/plugins.ts packages/server-contract/src/api/plugins.ts apps/server/src/routes/plugins.ts apps/cli/src/commands/plugin.ts (empty — not fixed on origin/main a108fa7ef as of 2026-08-18)
fc3454809's diff of the contract and the route message
export const pluginInstallSourceRequestSchema = z
- .object({ source: z.string().min(1) })
+ .object({
+ source: z.string().min(1),
+ selection: pluginSourceSelectionSchema.default(
+ ROOT_PLUGIN_SOURCE_SELECTION,
+ ),
+ })
.strict();
…
- error: 'expected { "source": string }',
+ error:
+ 'expected { "source": string, "selection"?: { "kind": "root" } | { "kind": "subdirectory", "path": string } | { "kind": "entry", "name": string } }',
…
- const body = pluginInstallSourceRequestSchema.parse(input);
+ const body = pluginInstallSourceRequestSchema.parse({
+ source: input.source,
+ selection: pluginSourceSelection(input),
+ });
Patched-SDK experiment (shows the second skew layer)
$ git apply 1662/repro/proposed-fix-sdk-omit-root-selection.patch && pnpm exec turbo run build --filter=@bb/cli
$ curl -s -X DELETE http://localhost:25737/api/v1/plugins/issue-1662-fixture
$ unset BB_CLI; BB_SERVER_URL=http://localhost:25737 node packages/scripts/dist/commands/run-cli.js plugin install /tmp/bb-1662-plugin --yes
Installing issue-1662-fixture@0.1.0 from /tmp/bb-1662-plugin
Plugins are full-trust code running inside the BB server. They can read all local BB data, including other plugins' secrets.
Error: [
{
"expected": "string",
"code": "invalid_type",
"path": [ "plugin", "publisherLabel" ],
"message": "Invalid input: expected string, received undefined"
}
]
exit=1
$ curl -s http://localhost:25737/api/v1/plugins | python3 -c "import sys,json; print([p['id'] for p in json.load(sys.stdin)['plugins']])"
['ask-user-question', 'automations', 'connect', 'custom-instructions', 'inline-vis', 'issue-1662-fixture', 'provider-retry', 'secrets', 'side-chat', 'workflows']
# → the request was accepted and the plugin installed; only the response parse failed.
$ git checkout -- packages/sdk/src/areas/plugins.ts packages/sdk/test/sdk.test.ts
0.37.0 server log excerpt
[07:18:05] INFO: [server] Server listening {"bindHost":"127.0.0.1","port":25737,"dataDir":"/tmp/bb-1662-old-data"}
[07:18:41] INFO: [server] plugin issue-1662-fixture@0.1.0 loaded # step 4 curl {source}
[07:19:40] INFO: [server] plugin issue-1662-fixture@0.1.0 loaded # step 2 (0.37.0 CLI)
[07:22:03] INFO: [server] plugin issue-1662-fixture@0.1.0 loaded # patched-SDK experiment
# no "loaded" line at all for the 0.38.0-CLI attempt (step 3): the request never passed validation.
All commands run (chronological, condensed)
gh issue view 1662 --repo get-bb/bb --json title,body,state,labels,comments,createdAt
git checkout --detach 16ceb3a54 && pnpm install --frozen-lockfile --prefer-offline && pnpm exec turbo run build
git log/show fc3454809, 1c3f3eff0, desktop-v0.37.0, desktop-v0.38.0 (contract, route, sdk)
scripts/bb-dev-app current # :25700 (new server)
node 1662/repro/capture-server.mjs 25799 & # HTTP sink
BB_SERVER_URL=http://localhost:25799 … run-cli.js plugin install /tmp/bb-1662-plugin --yes # body capture (16ceb3a54 CLI)
npx --yes -p bb-app@0.37.0 bb-server --data-dir /tmp/bb-1662-old-data --server-port 25737 &
BB_SERVER_URL=http://localhost:25737 npx -p bb-app@0.38.0 bb plugin install … --yes # 422 (bug)
BB_SERVER_URL=http://localhost:25737 npx -p bb-app@0.37.0 bb plugin install … --yes # OK (after unset BB_CLI)
BB_SERVER_URL=http://localhost:25799 npx -p bb-app@0.37.0 bb plugin install … --yes # body capture: {"source":…} only
curl POST :25737/api/v1/plugins/install {source} → 200 ; {source,selection} → 422
BB_SERVER_URL=http://localhost:25700 npx -p bb-app@0.37.0 bb plugin install … --yes # old CLI, new server: OK
BB_SERVER_URL=http://localhost:25700 … run-cli.js plugin install … --yes # new CLI, new server: OK
cd packages/sdk && pnpm exec vitest run test/issue-1662-install-body.test.ts # 1 failed (bug), 1 passed
apply patch → vitest run (92 passed) → turbo typecheck --filter=@bb/sdk → end-to-end vs :25737 → git checkout -- (revert)
kill 0.37.0 server; pnpm dev:stop