Skip to content

Make daemon-backed AppContext services nullable to catch runtime-backed null-deref crashes at compile time #407

@arul28

Description

@arul28

Severity: Medium · Verify confidence: high

File: apps/desktop/src/main/services/ipc/registerIpc.ts:652

What

The AppContext type declares laneService, diffService, fileService, agentChatService, gitService, conflictService, operationService, sessionService, ptyService, reviewService, keybindingsService, prService, etc. as NON-nullable (registerIpc.ts:647-694), but in runtime-backed/dormant mode they are all assigned null (main.ts:4285-4351, returned via as unknown as AppContext at main.ts:4351; runtime-backed context at main.ts:4224-4235). Because the cast erases the nullability, TypeScript cannot flag the ~326 unguarded ctx.<service>.method() dereferences in registerIpc.ts, which is precisely why this crash class keeps recurring (commits 0f21fa1, and the two unguarded chat handlers above). This is the structural root cause: every new IPC handler added against these services is a latent production crash with zero compiler help. Marking the daemon-backed services as | null in AppContext (and fixing the resulting type errors with guards) would convert this whole class from runtime crashes into compile errors.

Trigger

Static: the dormant/runtime-backed contexts assign null to services typed as non-null and cast through as unknown as AppContext, so tsc accepts unguarded dereferences that crash at runtime in production. Tests pass because shouldUseInProcessProjectRuntime() is true only when NODE_ENV==='test' (main.ts:1222-1223), so tests always get the fully-populated in-process context and never the null-service path.

Verification (adversarial)

Independently confirmed every claim. (1) AppContext types ~28 daemon-side services as REQUIRED, NON-nullable ReturnType<...> (no | null, no ?): laneService (registerIpc.ts:652), sessionService (662), ptyService (664), diffService (665), fileService (666), operationService (667), gitService (668), conflictService (669), agentChatService (671), prService (679), reviewService (685), keybindingsService (647), and more — verified by classifying lines 637-721. (2) createDormantProjectContext assigns null to all of them and returns as unknown as AppContext (main.ts:4275-4351). This cast is load-bearing: with strict:true (strictNullChecks on, verified in apps/desktop/tsconfig.json:12), laneService: null against a non-null type is a compile error without the cast. (3) The production-default context, initRuntimeBackedProjectContext, is built from the dormant context (const shellContext = createDormantProjectContext(projectRoot), main.ts:4130), spreads ...shellContext and overrides only logger/project/projectId/adeDir/hasUserSelectedProject/adeCliService/builtInBrowserService/macosVmService/usageTrackingService (main.ts:4224-4235) — it NEVER repopulates laneService/diffService/gitService/fileService/sessionService/agentChatService/etc., so those stay null, then returns as AppContext. (4) shouldUseInProcessProjectRuntime() returns true only when NODE_ENV==='test' (main.ts:1222-1223); project open routes to initContextForProjectRoot (fully-populated) in tests vs initRuntimeBackedProjectContext (null services) in production (main.ts:4948-4962), so tests never exercise the null path. (5) getCtx() returns AppContext typed non-null, so the 242 unguarded ctx.<service>.method() dereferences for the core services (370 across the broader set; ZERO optional-chained) compile cleanly despite null at runtime. (6) The crash class is demonstrably real and recurring: commit 0f21fa1 fixed ade.diff.getChanges: Cannot read properties of null (reading 'getChanges') on every lane in runtime-backed mode, and the diff IPC handlers at registerIpc.ts:6968-7007 STILL dereference ctx.diffService directly with no guard — protected only by preload routing convention that regressed in #393. This is correctly characterized as the structural root cause: the cast erases nullability so tsc gives zero help, and every new handler against these services is a latent production crash. Could not refute any element. Severity stays medium: it is a type-safety/design root cause rather than a single directly-triggerable defect, but it concretely and repeatedly enables high-impact production crashes, and the proposed fix (mark daemon services | null and guard) would convert the whole class into compile errors.


Filed from the 2026-05-29 ADE codebase audit (adversarially verified).

Metadata

Metadata

Assignees

Labels

bugSomething isn't working

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions