← reports

#2547 · bb-plugin-authoring skill documents bb.sdk.threads.list as returning { threads }; it returns an array

Type: Bug Priority: Medium Effort: Low documentation plugins open on GitHub 2026-08-27 · base ad79bbb5ec90

Verdict: REPRODUCED · Root-cause confidence: high

1. TL;DR

The bundled skill tells an author to destructure { threads } from bb.sdk.threads.list().

The SDK returns a plain array, so TypeScript rejects the example.

JavaScript runs the same line without an exception, but the threads value becomes undefined.

The existing documentation test checks API names only, so it did not check the example return type.

2. Claims vs findings

Claim from the issueStatusEvidence
The skill uses const { threads } = await bb.sdk.threads.list(...). Verified The base skill contains that text at line 689.
threads.list returns a plain array. Verified The SDK aliases the result to ThreadListResponse. The contract defines that response with z.array(...).
TypeScript rejects the documented line. Verified The saved probe returned TS2339 at the destructured property.
JavaScript returns undefined without an exception. Verified The runtime proof used an array and printed destructured threads: undefined.
The existing documentation test cannot detect this error. Verified The base test uses toContain for API names. It does not compile skill examples.
A later origin/main commit already fixed the issue. Refuted The path log from the base commit to origin/main contained no fix.

3. Environment

4. Minimal reproduction

  1. Check out and prepare the base revision.
    git checkout ad79bbb5ec909524f8f281e62d860c588a86f332
    pnpm install --frozen-lockfile --prefer-offline
    pnpm exec turbo run build:types --filter=@get-bb/plugin-sdk
  2. Save all three required files in one directory.

    For verification from the local report checkout, run:

    repo_root="$PWD"
    repro_dir=$(mktemp -d /tmp/bb-2547-repro-XXXXXX)
    cp /tmp/bb-reports/issues/2547/repro/documented-example.ts "$repro_dir/documented-example.ts"
    cp /tmp/bb-reports/issues/2547/repro/reproduce.mjs "$repro_dir/reproduce.mjs"
    cp /tmp/bb-reports/issues/2547/repro/runtime-proof.mjs "$repro_dir/runtime-proof.mjs"
    cd "$repro_dir"

    After publication, the same files are available with these commands:

    base_url="https://get-bb.github.io/reports/issues/2547/repro"
    curl -fsSL "$base_url/documented-example.ts" -o documented-example.ts
    curl -fsSL "$base_url/reproduce.mjs" -o reproduce.mjs
    curl -fsSL "$base_url/runtime-proof.mjs" -o runtime-proof.mjs

    Files: documented-example.ts, reproduce.mjs, and runtime-proof.mjs.

  3. Run the compiler probe and print its exit status.
    set +e
    node ./reproduce.mjs "$repo_root" 2>&1 | sed -E $'s/\x1B\\[[0-9;]*[mGK]//g'
    tsc_exit=${PIPESTATUS[0]}
    printf 'tsc_exit=%s\n' "$tsc_exit"

    The command produced this complete output:

    documented-example.ts:7:11 - error TS2339: Property 'threads' does not exist on type '{ activity: { activeBackgroundAgentCount: number; activeBackgroundCommandCount: number; activeGoalCount: number; activePlanModeCount: number; activeWorkflowCount: number; }; archivedAt: number | null; ... 25 more ...; visibility: "hidden" | "visible"; }[]'.
    
    7   const { threads } = await bb.sdk.threads.list({ projectId, limit: 50 });
                ~~~~~~~
    tsc_exit=1

    The complete saved output is in compile-output.txt.

  4. Run the JavaScript return-shape proof.
    node ./runtime-proof.mjs
    Array.isArray(result): true
    result.length: 1
    destructured threads: undefined

    See runtime-proof.mjs and runtime-output.txt.

The compile probe uses the actual bundled SDK declaration from the supplied bb checkout.

Probe source

import type { BbPluginApi } from "@get-bb/plugin-sdk";

declare const bb: BbPluginApi;
declare const projectId: string;

async function documentedExample(): Promise<void> {
  const { threads } = await bb.sdk.threads.list({ projectId, limit: 50 });
  console.log(threads);
}

void documentedExample;

5. Root cause

The fault is documentation drift, not an SDK or server response fault.

The skill treats the array response as an object with a threads property.

The SDK result type is a direct alias:

export type ThreadListResult = ThreadListResponse;

See packages/sdk/src/areas/threads.ts.

The public method returns that result:

list(args?: ThreadListArgs): Promise<ThreadListResult>;

See the ThreadsArea contract.

The server contract defines the response as an array:

export const threadListResponseSchema = z.array(threadListEntrySchema);

See packages/server-contract/src/api/threads.ts.

The SDK implementation returns the route JSON without an object wrapper.

See the list implementation.

The deeper cause is a test gap.

The base test checks that the skill contains each API name.

It does not compile a code example against the SDK contract.

See the existing documentation assertions.

6. Proposed fix (first principles)

  1. Change the example to const threads = await bb.sdk.threads.list(...).
  2. Extract documented SDK calls during tests.
  3. Compile each documented destructuring pattern against BbPluginApi["sdk"].
  4. Keep the SDK contract and server response unchanged.

This change needs no daemon protocol version increase because it changes no wire data.

7. PR review

PR #2550 · Unblock first-plugin authoring

Verdict: MERGE.

The PR changes the documented line to the correct array assignment.

It also adds a type-driven regression test for documented SDK destructuring.

I restored the bad line temporarily, and the new test failed with TS2339.

I restored the fix, and both documentation test files passed all 16 tests.

Current main was 1d97c63ed13b5231c1051b7af7ac36661d1f65ab during this review.

git merge-tree --write-tree merged PR head 0ee3c8bae68a8473a3b82e1c1b9f76686b26a2a4 with that commit without a conflict.

GitHub also reported the PR as mergeable. Its merge-state status was UNSTABLE, not conflicted.

SeverityFindingResult
Correct SKILL.md:689 assigns the array directly. This change fixes the reported root cause.
Correct The new test fails on the base example and passes after the fix. The test protects the exact regression.
Low The new probe does not compile method arguments or later property access. This gap does not affect the reported destructuring error. A later test can compile complete code blocks.
Low The scaffold source repeats the same dependency comment before cron-parser and hono. This duplication does not change behavior.

Checks run on PR #2550

I did not reproduce the eight timeout failures on the base revision.

The failures did not touch the changed files, and the focused server tests passed.

Logs: build, CLI tests, server tests, reverted test, and fixed tests.

8. Related issues

9. Appendix

History

Commit 010ef9cd455382c0183d7d5784ece671ad601b18 introduced the wrong example.

That commit documented the SDK area map but did not compile the new example.

Commands run

gh issue view 2547 --comments
gh issue view 2547 --json number,title,body,labels,projectItems,comments,state,url
pnpm install --frozen-lockfile --prefer-offline
pnpm exec turbo run build
git fetch origin main
git log ad79bbb5ec90..origin/main --oneline -- <affected paths>
git fetch origin refs/pull/2550/head:refs/remotes/origin/pr-2550
git merge-tree --write-tree origin/main refs/remotes/origin/pr-2550
node issues/2547/repro/reproduce.mjs <repo-root>
node issues/2547/repro/runtime-proof.mjs
gh pr view 2550
gh pr diff 2550
gh pr checkout 2550
pnpm exec turbo run build
pnpm exec turbo run test --filter=@bb/cli --force
pnpm exec turbo run test --filter=@bb/server --force
pnpm exec vitest run test/services/plugins/plugin-authoring-doc-examples.test.ts test/services/plugins/plugin-authoring-docs.test.ts
git blame ad79bbb5ec90 -L 685,697 -- apps/server/src/services/skills/builtin-skills/bb-plugin-authoring/SKILL.md

Saved reproduction artifacts

Verification

The verifier reproduced TS2339 from Steps 1 through 3.

The verifier found that Step 4 lacked a command to save runtime-proof.mjs.

The revised steps now copy or download all three files before either probe runs.

I ran the revised local copy commands in a new directory.

The compiler returned status 1, and the runtime proof printed the saved output.

I also checked PR #2550 against current main with git merge-tree. The check found no conflict.