#3467 · SDK installer response dispatch
Bug · Priority: Medium · Effort: Low · plugins
Issue · 2026-09-11 · Base b04091eca67bb463cce16b9bfa318660bd20ab69
REPRODUCED · Root-cause confidence: high
TL;DR
The SDK fails to return installer events when its transport returns a native Fetch response while the global Response constructor is Hono’s server wrapper. It invokes the global prototype’s text reader with a foreign receiver. Hono’s reader expects a private symbol that native responses lack, producing the reported exception. Two clean checkouts reproduced the same exception. Dispatching through the returned response fixes the failure.
Claims vs findings
| Claim | Finding | Evidence |
|---|---|---|
| Installer events fail to return with a native response | Verified | Direct SDK reproduction, repeated on clean base. |
| The failure comes from wrapper-specific state | Verified | Installed Hono adapter reader calls this[getResponseCache](). |
| The CLI remains unchanged | Unverified | No installer executed. Response parsing occurs after the request; this exception alone does not prove installation failed. |
| Version-specific macOS failure | Partially verified | Current trusted main on macOS reproduces; reported release artifacts were not executed. |
Environment
macOS / Darwin arm64; Node 22.22.3; pnpm 9.15.0; locked @hono/node-server 1.19.14. Trusted origin/main commit above. Frozen dependency installation and full Turbo build succeeded (20 tasks). No app instance, network listener, provider process, credentials, or runtime database was used. Fetch is supplied by the test and returns an actual native Response.
Minimal reproduction
- Clone get-bb/bb and check out the base commit above.
- Run
pnpm install --frozen-lockfile --prefer-offlineandpnpm exec turbo run build --filter=@bb/sdk. - Save the inline script below as repro-3467.mjs in the repository root.
- Run
node --conditions=source --import tsx repro-3467.mjs.
Expected: two parsed installer events and exit 0. Actual on both base checkouts: exit 1:
TypeError: this[getResponseCache] is not a function
import assert from "node:assert/strict";
import { createRequire } from "node:module";
import { dirname, join } from "node:path";
import { pathToFileURL } from "node:url";
import { createBbSdk } from "./packages/sdk/src/core.ts";
import { createHttpTransport } from "./packages/sdk/src/transport-http.ts";
const requireServer = createRequire(new URL("./apps/server/package.json", import.meta.url));
const adapter = join(dirname(requireServer.resolve("@hono/node-server")), "response.js");
const { Response: ServerResponse } = await import(pathToFileURL(adapter).href);
const NativeResponse = globalThis.Response;
const events = [
{ type: "started", provider: "test-provider", command: "test-installer" },
{ type: "completed", provider: "test-provider", exitCode: 0, signal: null, success: true },
];
const sdk = createBbSdk({
transport: createHttpTransport({
baseUrl: "http://bb.test",
runtime: "node",
fetch: async () => new NativeResponse(events.map((event) => JSON.stringify(event)).join("\n")),
}),
});
globalThis.Response = ServerResponse;
try {
const result = await sdk.hosts.installProviderCli({ hostId: "test-host", provider: "test-provider", actionKind: "update" });
assert.deepEqual(result, events);
console.log("PASS: parsed both installer events");
} catch (error) {
console.log(`${error.name}: ${error.message}`);
process.exitCode = 1;
} finally {
globalThis.Response = NativeResponse;
}
The focused regression test below uses a replacement Response class with private state, avoiding a new SDK dependency on the server adapter. Copy it to packages/sdk/test/ and run pnpm exec turbo run test --filter=@bb/sdk -- host-installer-response.test.ts. It fails on unchanged production code at the event-return assertion with a private-member receiver error.
Root cause
The installer SDK method obtains a transport response but calls the global Response prototype’s text method. Transport resolution returns the original successful response. The server starts Hono’s adapter with its default global-object replacement enabled. The locked adapter’s dist/response.js implements text using this[getResponseCache]()[k](); a native response has no such symbol. The reproduction imports that installed wrapper directly without starting a server.
Proposed fix
Call the returned response’s own text method. Give the resolved response the existing SdkResponseLike interface because Hono’s generated client infers the streamed endpoint text as never. This is an internal annotation with no wire or public API change. The fix passes the exact reproduction and all 105 SDK tests plus SDK typechecking.
Verification
The same agent repeated the reproduction in a second clean temporary Git worktree at the same base SHA, with a separate frozen dependency installation and successful SDK build. The command above again exited 1 with the exact same exception. No ports or data directories were needed. No report correction was required; the claim that the installer itself remained unchanged remains unverified.
Related issues
Issue #3466 has the same reported symptom; it was not executed or modified. No open PR linked to #3467 was found during investigation.
Appendix
Issue content was treated as untrusted claims; its suggested implementation and test were not executed. The reproduction was authored from trusted repository contracts and adapter code.
import { afterEach, describe, expect, it, vi } from "vitest";
import { createBbSdk } from "../src/core.js";
import { createHttpTransport } from "../src/transport-http.js";
const NativeResponse = globalThis.Response;
class AlternateResponse extends NativeResponse {
#readText = () => super.text();
override text(): Promise<string> {
return this.#readText();
}
}
afterEach(() => {
vi.unstubAllGlobals();
});
describe("host installer response readers", () => {
it("reads native installer events when the global Response is replaced", async () => {
const events = [
{ type: "started", provider: "test-provider", command: "test-installer" },
{
type: "output",
provider: "test-provider",
stream: "stdout",
text: "ready",
},
{
type: "completed",
provider: "test-provider",
exitCode: 0,
signal: null,
success: true,
},
];
const response = new NativeResponse(
events.map((event) => JSON.stringify(event)).join("
") + "
",
);
const sdk = createBbSdk({
transport: createHttpTransport({
baseUrl: "http://bb.test",
runtime: "node",
fetch: async () => response,
}),
});
vi.stubGlobal("Response", AlternateResponse);
await expect(
sdk.hosts.installProviderCli({
hostId: "test-host",
provider: "test-provider",
actionKind: "update",
}),
).resolves.toEqual(events);
expect(response.bodyUsed).toBe(true);
});
});
Before: 1 focused test failed (private receiver state error). Second clean reproduction: TypeError: this[getResponseCache] is not a function After: 8 test files passed; 105 tests passed; SDK typecheck passed. Direct reproduction after fix: PASS: parsed both installer events
Commands: frozen install; full base Turbo build; SDK build in second checkout; focused test before production changes; Turbo SDK test/typecheck after changes; direct reproduction before, second checkout, and after; git diff --check. A broken host pnpm launcher was bypassed with a temporary Corepack launcher. No dependency or lockfile changes were made.