Summary
cbm_pipeline_pass_lsp_cross enters what appears to be an unrecoverable deadlock (uninterruptible kernel state on macOS, not even kill -9 reaps the process) on at least one specific .ts file in a real-world 885-file SvelteKit + Turborepo monorepo. The pass logs pass.start pass=lsp_cross files=885, processes ~450 files in a few seconds, hits one particular file, and then stops advancing for at least 600 s (no further log lines, no CPU consumption beyond the initial ~3 s, no file/syscall activity visible in truss/fs_usage).
The process can't be killed with SIGTERM or SIGKILL — ps reports state UE (uninterruptible exit) and the PID survives until reboot.
Reproducer
Repository is private but the trigger file is small and pasted below. The monorepo characteristics that seem to matter:
- ~174k total defs across ~700 modules (large
all_defs[] array fed to cbm_run_ts_lsp_cross)
- Heavy use of
export * from './...' re-export chains via index.ts barrel files (workspace package entry points)
@<org>/<pkg> workspace imports throughout
Repro command:
codebase-memory-mcp cli index_repository '{\"repo_path\":\"/path/to/monorepo\",\"incremental\":false}' 2>&1 | tee /tmp/idx.log
The pass hangs on packages/twenty-client/src/opportunities.ts (94 lines, 3 KB), which imports two siblings (./client, ./filters, ./people) and exports two top-level async functions plus an inner toCurrency arrow.
Workaround: I added a getenv(\"CBM_DISABLE_LSP_CROSS\") guard in run_parallel_pipeline to skip the pass entirely. With it set, indexing finishes in ~1.3 s and the rest of the pipeline produces correct results (the stock 2026-04-05 binary I was previously running didn't have lsp_cross wired up at all, so this is effectively the pre-LSP behaviour).
Reproducer file
import { call, type TwentyClient } from './client';
import { quoteFilterValue } from './filters';
import { upsertContact } from './people';
type TwentyOpportunity = {
id: string;
[key: string]: unknown;
};
type OpportunityListResponse = { data: { opportunities: TwentyOpportunity[] } };
type OpportunityResponse = {
data: { createOpportunity?: TwentyOpportunity; updateOpportunity?: TwentyOpportunity };
};
export type UpsertDealParams = {
licenseId: string;
stage: string;
contactEmail?: string;
contactId?: string;
mrr?: number;
customFields?: Record<string, unknown>;
};
export type UpsertDealResult = { twentyDealId: string };
export async function upsertDeal(
client: TwentyClient,
params: UpsertDealParams
): Promise<UpsertDealResult | null> {
const existing = await findOpportunityByLicenseId(client, params.licenseId);
const personId = await resolvePersonId(client, params);
if (!personId && !existing) return null;
const toCurrency = (euros: number) => ({
amountMicros: Math.round(euros * 1_000_000),
currencyCode: 'EUR'
});
const body: Record<string, unknown> = {
stage: params.stage.toUpperCase(),
anoLicenseId: params.licenseId,
...(params.mrr !== undefined ? { mrr: toCurrency(params.mrr) } : {}),
...(personId ? { pointOfContactId: personId } : {}),
...(params.customFields ?? {})
};
// ... (HTTP call PATCH / POST etc.)
}
(Full file ~ 94 lines, available on request.)
Observed log
level=info msg=pass.start pass=lsp_cross files=885
level=info msg=lsp_cross.file i=447 rel=packages/twenty-client/src/filters.ts lang=3
level=info msg=lsp_cross.file i=448 rel=packages/twenty-client/src/shape.test.ts lang=3
level=info msg=lsp_cross.file i=449 rel=packages/twenty-client/src/http.ts lang=3
level=info msg=lsp_cross.file i=450 rel=packages/twenty-client/src/people.ts lang=3
level=info msg=lsp_cross.file i=451 rel=packages/twenty-client/src/opportunities.ts lang=3
# 600+ s of silence after this line
Per-file log was added locally to localise the hang (one cbm_log_info call at the start of the for-loop body in cbm_pipeline_pass_lsp_cross).
Environment
- macOS 15.x (Darwin 25.3.0), Apple Silicon (arm64)
- Built with stock toolchain (Apple Clang) via
make -f Makefile.cbm cbm on commit 6226972
- 16 GB RAM, default mem budget (8 GB)
- File
packages/twenty-client/src/opportunities.ts: 94 lines, plain TypeScript, no decorators, three relative imports
What I'd suggest investigating
pxc_run_one_ts allocates a CBMArena scratch and calls cbm_run_ts_lsp_cross with the full all_defs[] (174k entries here). Maybe the type registry build is O(N²) on the re-export chain depth, or a cycle in export * from cross-references confuses the LSP resolver.
- A timeout / watchdog inside
cbm_run_ts_lsp_cross (e.g. bail with a lsp_cross.timeout log line after N seconds) would degrade gracefully instead of hanging the whole indexing pass.
- Less ambitiously: skip files where
def_count * file_count > 10^8 (or similar O(N²) gate) to avoid pathological inputs entirely.
I can't share the proprietary repo but I'm happy to bisect what part of opportunities.ts is triggering the deadlock if you can suggest specific patterns to test (e.g. type aliases vs interfaces, async arrow inside async function, etc.).
Workaround docs
The CBM_DISABLE_LSP_CROSS=1 environment guard I added is part of the workaround patch in #369 (well, actually it's not in that PR — it's a separate local change). Happy to extract that as a small additional PR if you'd like a documented escape hatch in upstream.
Generated with Claude Code
Summary
cbm_pipeline_pass_lsp_crossenters what appears to be an unrecoverable deadlock (uninterruptible kernel state on macOS, not evenkill -9reaps the process) on at least one specific.tsfile in a real-world 885-file SvelteKit + Turborepo monorepo. The pass logspass.start pass=lsp_cross files=885, processes ~450 files in a few seconds, hits one particular file, and then stops advancing for at least 600 s (no further log lines, no CPU consumption beyond the initial ~3 s, no file/syscall activity visible intruss/fs_usage).The process can't be killed with
SIGTERMorSIGKILL—psreports stateUE(uninterruptible exit) and the PID survives until reboot.Reproducer
Repository is private but the trigger file is small and pasted below. The monorepo characteristics that seem to matter:
all_defs[]array fed tocbm_run_ts_lsp_cross)export * from './...'re-export chains via index.ts barrel files (workspace package entry points)@<org>/<pkg>workspace imports throughoutRepro command:
The pass hangs on
packages/twenty-client/src/opportunities.ts(94 lines, 3 KB), which imports two siblings (./client,./filters,./people) and exports two top-level async functions plus an innertoCurrencyarrow.Workaround: I added a
getenv(\"CBM_DISABLE_LSP_CROSS\")guard inrun_parallel_pipelineto skip the pass entirely. With it set, indexing finishes in ~1.3 s and the rest of the pipeline produces correct results (the stock 2026-04-05 binary I was previously running didn't havelsp_crosswired up at all, so this is effectively the pre-LSP behaviour).Reproducer file
(Full file ~ 94 lines, available on request.)
Observed log
Per-file log was added locally to localise the hang (one
cbm_log_infocall at the start of the for-loop body incbm_pipeline_pass_lsp_cross).Environment
make -f Makefile.cbm cbmon commit 6226972packages/twenty-client/src/opportunities.ts: 94 lines, plain TypeScript, no decorators, three relative importsWhat I'd suggest investigating
pxc_run_one_tsallocates aCBMArena scratchand callscbm_run_ts_lsp_crosswith the fullall_defs[](174k entries here). Maybe the type registry build is O(N²) on the re-export chain depth, or a cycle inexport * fromcross-references confuses the LSP resolver.cbm_run_ts_lsp_cross(e.g. bail with alsp_cross.timeoutlog line after N seconds) would degrade gracefully instead of hanging the whole indexing pass.def_count * file_count > 10^8(or similar O(N²) gate) to avoid pathological inputs entirely.I can't share the proprietary repo but I'm happy to bisect what part of
opportunities.tsis triggering the deadlock if you can suggest specific patterns to test (e.g. type aliases vs interfaces, async arrow inside async function, etc.).Workaround docs
The
CBM_DISABLE_LSP_CROSS=1environment guard I added is part of the workaround patch in #369 (well, actually it's not in that PR — it's a separate local change). Happy to extract that as a small additional PR if you'd like a documented escape hatch in upstream.Generated with Claude Code