Added by: blind-spot analysis (April 2, 2026) Technique: Import graph analysis, data flow tracing, singleton detection, circular dependency audit
These are the architectural pillars — the modules everything depends on. Breaking changes here cascade everywhere.
| Rank | Module | Import Count | Role |
|---|---|---|---|
| 1 | react |
753 | UI framework |
| 2 | react/compiler-runtime |
395 | React compiler optimization |
| 3 | path |
250 | File path manipulation |
| 4 | ink.js |
240+145 = 385 | Terminal UI rendering (two path variants) |
| 5 | bun:bundle |
196 | Feature flag system (build-time DCE) |
| 6 | Tool.js |
157 | Core tool definition/execution framework |
| 7 | fs/promises |
136 | Async file operations |
| 8 | zod/v4 |
125 | Schema validation |
| 9 | commands.js |
124 | Command registry |
| 10 | crypto |
120 | Cryptographic operations |
| 11 | bootstrap/state.js |
105 | Global session state singleton |
| 12 | debug.js |
96+85 = 181 | Debug logging (multiple path variants) |
| 13 | utils/errors.js |
93 | Error handling utilities |
| 14 | figures |
89 | Unicode box drawing characters |
| 15 | utils/log.js |
87 | Logging infrastructure |
| 16 | utils/slowOperations.js |
80 | Performance measurement wrapper |
| 17 | utils/lazySchema.js |
58 | Deferred schema validation |
| 18 | axios |
57 | HTTP client |
Key insight: ink.js (terminal rendering), Tool.js (execution), bootstrap/state.js (globals), and commands.js form the core architectural quadrant. Multiple path variants for ink.js and debug.js indicate path aliasing complexity.
bootstrap/state.ts is the single point of truth for ~250+ mutable fields. 30+ files import and call setters on this state. No centralized transaction log. No undo capability.
interactionTimeDirty: booleanoutputTokensAtTurnStart: numbercurrentTurnTokenBudget: number | nullbudgetContinuationCount: numberscrollDraining: booleanscrollDrainTimer: ReturnType<typeof setTimeout> | undefined
- Session metrics (cost, duration, tool counts)
- Auth tokens:
oauthTokenFromFd,apiKeyFromFd,sessionIngressToken - Telemetry counters (meter, sessionCounter, costCounter)
- Plugin metadata and hook registrations
- Slow operation traces
Direct field assignment — no middleware, no event log:
export function setOauthTokenFromFd(token: string | null): void {
STATE.oauthTokenFromFd = token // Direct mutation, no audit trail
}All mutable singletons across the codebase use simple boolean or null flags, not proper locks/mutexes. History flush (history.ts:isWriting) uses a boolean flag, not a real lock.
- Entry: File descriptor →
utils/authFileDescriptor.ts:readTokenFromWellKnownFile() - Storage:
bootstrap/state.ts:setOauthTokenFromFd()→ in-memory STATE - Retrieval:
utils/auth.ts:getOAuthTokenFromFileDescriptor()→ returns to Anthropic SDK - Subprocess Fallback: Writes to disk at
~/.claude/remote/.oauth_token(mode 0o600)
- Entry:
setApiKeyFromFd()or env varANTHROPIC_API_KEY - Validation:
utils/auth.ts:getAuthToken()checks: OAuth FD → apiKeyHelper (settings) → env var - Passed to:
@anthropic-ai/sdkwith no intermediate encryption or access control wrapper
Finding: OAuth tokens and API keys are mixed into the STATE singleton without separate encryption or access control. Both are passed to external SDKs without intermediate wrapping.
| File | Try/Catch Count | Pattern |
|---|---|---|
utils/nativeInstaller/installer.ts |
50 | Sequential retries with fallbacks |
utils/sessionStorage.ts |
35 | Recovery from corrupted state |
services/mcp/client.ts |
29 | MCP server connection recovery |
utils/plugins/marketplaceManager.ts |
30 | Plugin download/install fallbacks |
main.tsx |
26 | Bootstrap-stage error handling |
ink/ink.tsx:} catch { /* stream may be destroyed */ }ink/components/App.tsx:} catch (error) { // In Bun, uncaught throw...main.tsx:} catch { // Silently ignore errors - this is just for analytics }upstreamproxy/relay.ts:} catch { // already closing }
MCP client errors use exponential backoff but swallow connection failures silently, potentially masking persistent auth issues.
Centralized in utils/signal.ts with 15+ channels:
| Signal | File | Purpose |
|---|---|---|
sessionSwitched |
bootstrap/state.ts | Session lifecycle |
keybindingsChanged |
keybindings/loadUserBindings.ts | File watcher on ~/.claude/keybindings.json |
settingsChanged |
utils/settings/changeDetector.ts | Config change propagation |
cooldownExpired |
utils/fastMode.ts | Rate limiter expiry |
queueChanged |
utils/messageQueueManager.ts | Message dispatch |
classifierChecking |
utils/classifierApprovals.ts | Auto-mode classifier UI |
skillsChanged |
utils/skills/skillChangeDetector.ts | Skill file watcher |
settingsChanged is subscribed by both plugin loader and permissionsLoader — implicit coupling.
Primary pattern: bootstrap/state.ts → Tools → Tool.ts → state/AppState.tsx
Mitigated by type-only imports:
// Tool.ts line 78
import type { AppState } from './state/AppState.js' // Type-only, no runtime cycleAdditional mitigation via utils/lazySchema.js (58 importers) — defers heavy Zod schema binding to prevent circular dependency at module evaluation time.
| File | Mutable State | Risk |
|---|---|---|
history.ts |
pendingEntries[], isWriting, currentFlushPromise |
Race condition on multi-flush |
bridge/bridgeDebug.ts |
debugHandle, faultQueue[] |
Cross-process without serialization |
ink/terminal-focus-state.ts |
focusState, resolvers, subscribers |
Promise race on multiple resolvers |
utils/sessionActivity.ts |
activityCallback |
Last setter wins |
context/notifications.tsx |
currentTimeoutId |
No queue, simplistic timeout |
All use boolean/null flags instead of proper synchronization primitives.