#3484 · Desktop shell probe exceeds its timeout

Bug · High priority · Low effort · desktop · 2026-09-11

GitHub issue · Base fa1f44ebe9e5676004b669e48c99b3c7606466b6

REPRODUCED · Root-cause confidence: high

TL;DR

The desktop startup path waits synchronously for an interactive login shell before continuing initialization. Its two-second timeout does not bound that wait when the shell ignores the default termination signal. A controlled six-second shell startup held the actual source function for 6,003 ms in each of two clean checkouts. Selecting SIGKILL reduced the same test to 2,003 ms. This is a source-level reproduction on macOS, not a packaged-app visual launch test.

Claims vs findings

ClaimFindingEvidence
The probe can exceed its timeout.VerifiedTwo actual source runs: 6,003 ms against a 2,000 ms timeout.
Interactive shell termination causes the blocked wait.VerifiedReal /bin/zsh, finite builtin-only startup, and a one-line kill-signal change yielding 2,003 ms.
Heavy user configuration causes missing provider executables.UnverifiedNo user configuration or provider discovery was exercised.
The packaged window never appears.Source-supported, not visually testedThe probe precedes desktop initialization; this experiment intentionally uses finite startup.

Environment

Darwin arm64, Node v22.22.3, system /bin/zsh. No server, provider, ports, or application data. Each run creates and removes a new temporary ZDOTDIR. Trusted source was fetched from get-bb/bb main. Repository visibility is public.

Minimal reproduction

  1. Check out get-bb/bb at fa1f44ebe9e5676004b669e48c99b3c7606466b6.
  2. Save the inline probe below as probe.mjs locally, then run from the repository root:
    node --experimental-strip-types /path/to/probe.mjs
  3. Expect the two-second probe to return within a generous 3.5-second allowance. Actual result in both runs:
    {"elapsedMs":6003,"result":{"kind":"unchanged","reason":"shell-error"}}
    AssertionError: 2-second probe exceeded 3.5-second allowance: 6003ms

The fixture uses a six-second builtin loop, so it finishes without an external child or an indefinitely stuck process. It does not execute any submitted patch or issue test.

import assert from 'node:assert/strict';
import { mkdtempSync, writeFileSync, rmSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { join, resolve } from 'node:path';
import { pathToFileURL } from 'node:url';

const { ensurePackagedUserShellPath } = await import(pathToFileURL(resolve('apps/desktop/src/desktop-shell-path.ts')));
const directory = mkdtempSync(join(tmpdir(), 'bb-shell-timeout-'));
const previous = process.env.ZDOTDIR;
try {
  writeFileSync(join(directory, '.zshrc'), 'integer finish=$((SECONDS + 6))\nwhile (( SECONDS < finish )); do :; done\n');
  process.env.ZDOTDIR = directory;
  const started = performance.now();
  const result = ensurePackagedUserShellPath({
    env: { PATH: '/usr/bin:/bin' },
    isPackaged: true,
    platform: 'darwin',
    logger: { warn() {} },
  });
  const elapsed = Math.round(performance.now() - started);
  console.log(JSON.stringify({ elapsedMs: elapsed, result }));
  assert.ok(elapsed < 3500, `2-second probe exceeded 3.5-second allowance: ${elapsed}ms`);
} finally {
  if (previous === undefined) delete process.env.ZDOTDIR;
  else process.env.ZDOTDIR = previous;
  rmSync(directory, { recursive: true, force: true });
}

Root cause

The source spawnSync call sets timeout but leaves killSignal at its default. The caller requests an interactive login shell. The interactive zsh process survives the termination signal, so synchronous execution waits for its eventual exit. runDesktopApp invokes this before application initialization, explaining the startup block.

Proposed fix

Set killSignal to SIGKILL at the existing spawnSync boundary. Keep the existing two-second policy and fallback behavior. The controlled source reproduction passes in 2,003 ms after this change. The automated regression exercises the public function and restores environment state; it is macOS-only. No new public API, packaging changes, dependencies, or timeout-policy changes are needed.

Verification

The same agent repeated the source reproduction in a second clean clone at the exact base commit, with its own fresh temporary shell directory. The identical command failed with 6,003 ms again. No report correction was needed. Both source permalinks were checked against the recorded commit. A newer trusted main commit, 9ece01e61e71208cd7fb80a388ed1081c4230b3b, has identical probe source and also fails the newly written Vitest regression before the fix.

Related issues and pull requests

No linked open pull request was found in cross-reference metadata or the open-PR search for this issue. A small desktop-issue scan found no duplicate of this timeout mechanism.

Appendix

Untrusted-data note: submitted instructions, code, and suggested changes were treated only as claims; this reproduction was authored from trusted repository source.

Frozen install and the full Turbo build completed after substituting a temporary Corepack launcher for a broken host pnpm launcher. The build completed all 55 tasks. No dependency or lockfile changes were made. The post-fix desktop test and typecheck run passed: 43 test files, 324 tests passed, one skipped. The focused timeout regression passed on macOS.

Raw evidence is retained locally. The inline probe and results above are sufficient to repeat the test.

pnpm install --frozen-lockfile --prefer-offline
pnpm exec turbo run build
pnpm exec turbo run test --filter=@bb/desktop -- desktop-shell-path-timeout
pnpm exec turbo run test typecheck --filter=@bb/desktop