-
Notifications
You must be signed in to change notification settings - Fork 0
systems system services
Active contributors: Foundry core (src/main/system/)
System services are the small, privileged utilities that keep Foundry honest on a real Mac: environment diagnosis, user-visible outcomes, and child process hygiene. They do not sequence pipelines. They make sure the operator can tell why the app cannot run, hears when a run finishes, and does not leave ghost processes or forever-running rows after a crash.
src/main/system/
doctor.ts # environment and project checks
notify.ts # Notification + dock badge
procs.ts # spawn registry, kill tree, pid/command match
Callers: main.ts (relaunch sweep trigger, quit kill-all), context.ts (outcome + badge + needs-input), ipc.ts (doctor and maintenance), engine/registry.ts (sweep and kill using procs helpers).
| Module | Abstraction |
|---|---|
| Doctor |
DoctorCheck: id, label, ok, detail, optional fix action |
| Notify | Outcome notifications and dock badge driven only from run lifecycle hooks |
| Procs | In-memory registry of live children; pid + command verification for cross-launch kill |
App-level runDoctor(droidPath):
| Check | Meaning |
|---|---|
droid |
CLI runnable at configured path; fix opens droid quickstart docs |
auth |
FACTORY_API_KEY or ~/.factory/settings.json present |
git |
git --version on PATH |
macos |
Darwin major version at or above the supported floor (macOS 26 family check in code) |
Project-level checkProject(project):
| Check | Meaning |
|---|---|
path |
Folder still exists |
repo |
Path is a git repository |
base-ref |
Configured base ref resolves |
submodules |
Warns if .gitmodules present (worktrees do not populate them automatically) |
clean |
Base worktree porcelain empty (dirty base blocks automatic merge) |
commands |
At least one project command configured |
worktrees |
Leftover .foundry-worktrees entries; fix points at Maintenance |
Doctor runs at onboarding and from Settings. Results are plain data for DoctorList in the UI.
-
notifyOutcome: when a run reachesaccepted,rejected,failed, orkilled, if the matching settings flag is on and notifications are supported. Body is pipeline name plus a short request slice; subtitle is the branch when present. -
notifyNeedsInput: engineer or permission interrupts, gated bynotifications.needsInput. -
setDockBadge: macOS dock badge shows live run count whendockBadgeis enabled.
These are invoked from AppContext when the registry reports a finished run or a needs-input interrupt, so the banner, status, and notification share one finish path. See the engine's finish / finishRun settlement invariant in Architecture.
Every child the engine cares about should be:
-
Spawned (often via
spawnTracked) and registered with run id, pid, and full command string. -
Recorded in the trace
processestable (viaTracer.recordProcess) for relaunch visibility. - Unregistered on exit; ended in the trace when closed.
Kill semantics:
-
killTree: children first (pgrep -P), then parent, so killing a shell does not leave an orphaned tree. -
killRun: all pids currently registered for that run id. -
killAll: SIGKILL everything in the in-memory registry on app quit. -
commandMatches: before signalling a pid found only in the database (another launch), require thatpsstill shows a command containing the recorded head token. Recycled pids must not be killed.
flowchart TD
Spawn[spawnTracked / register] --> Live[In-memory registry]
Spawn --> Trace[processes table]
KillUI[runs.kill] --> KillRun[killRun / killTree]
Quit[before-quit] --> KillAll[killAll]
Relaunch[app.whenReady] --> Sweep[registry.sweep]
Sweep --> Open[openProcesses from trace]
Open --> Alive{pid alive and command matches?}
Alive -->|no| EndProc[endProcess row]
Alive -->|yes, run still running, not live| Fail[finishRun failed orphan]
On every launch, main.ts calls ctx.registry.sweep(projects):
- For each project's tracer, close process rows whose pid/command no longer match a live process.
- For each
runs.status = 'running'id that is not in the live in-memory map and has no remaining live processes, emit an error event andfinishRun(..., 'failed').
A run whose engine died with the app can never finish on its own; leaving it running would lie forever in the UI.
Separately, orphan worktrees (disk) are listed and removed through maintenance IPC (worktree.findOrphans / discard), not by procs.ts. Doctor's worktree check points operators at that Maintenance UI.
| System | Touchpoint |
|---|---|
| Trace |
processes table; finishRun during sweep |
| Engine / registry | register children, kill run, sweep, live count for badge |
| IPC |
doctor.run, maintenance orphans/retention/compact |
| Store | Notification flags, retention days, droid path |
| Renderer | DoctorList, settings notification toggles, interrupt sheet |
| Call | When |
|---|---|
runDoctor / checkProject
|
Onboarding, Settings, projects.check
|
notifyOutcome / notifyNeedsInput / setDockBadge
|
AppContext lifecycle hooks |
register / spawnTracked / killRun / killAll
|
Engine spawn and app quit |
commandMatches / isAlive
|
Sweep and safe cross-launch kill |
RunRegistry.sweep |
Once at app.whenReady
|
| Path | Role |
|---|---|
apps/desktop/src/main/system/doctor.ts |
App and project checks |
apps/desktop/src/main/system/notify.ts |
Notifications and dock badge |
apps/desktop/src/main/system/procs.ts |
Process registry and kill helpers |
apps/desktop/src/main/engine/registry.ts |
Relaunch sweep, kill run, live set |
apps/desktop/src/main/main.ts |
Sweep on ready; killAll on quit |
apps/desktop/src/main/context.ts |
Wires notify + badge to run events |
apps/desktop/src/main/ipc.ts |
Doctor and maintenance handlers |
- Trace (processes + finishRun)
- Engine
- IPC and preload
- Features: worktrees, onboarding (when present)
Overview
Snapshot and history
How to contribute
Apps
Systems
Features
Primitives
Background and security
Reference