From 3c79e28e32970c7362e2cb9edbcce96c228206aa Mon Sep 17 00:00:00 2001 From: bradygaster Date: Sun, 22 Mar 2026 00:43:02 -0700 Subject: [PATCH] fix: add memory safety caps and file watcher filtering (#259) Audit and mitigate resource-pressure patterns that could contribute to VS Code crashes during Squad execution (Issue #259, Discussion #174). Changes: - StreamingPipeline: cap usageData array at 1000 events (FIFO eviction) - TelemetryCollector: cap event queue at 500 events (FIFO eviction) - SquadObserver: filter orchestration-log/ and .git/ from fs.watch events - Add docs/reference/vscode-troubleshooting.md with root cause analysis, mitigations, and user-facing troubleshooting steps Closes #259 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../docs/reference/vscode-troubleshooting.md | 107 ++++++++++++++++++ .../squad-sdk/src/runtime/squad-observer.ts | 3 + packages/squad-sdk/src/runtime/streaming.ts | 7 ++ packages/squad-sdk/src/runtime/telemetry.ts | 8 ++ 4 files changed, 125 insertions(+) create mode 100644 docs/src/content/docs/reference/vscode-troubleshooting.md diff --git a/docs/src/content/docs/reference/vscode-troubleshooting.md b/docs/src/content/docs/reference/vscode-troubleshooting.md new file mode 100644 index 000000000..1747516c4 --- /dev/null +++ b/docs/src/content/docs/reference/vscode-troubleshooting.md @@ -0,0 +1,107 @@ +# VS Code Troubleshooting + +> ⚠️ **Experimental** — Squad is alpha software. APIs, commands, and behavior may change between releases. + +This page covers known issues and mitigations when running Squad inside VS Code's integrated terminal. + +--- + +## Symptom: VS Code closes unexpectedly during Squad execution + +**Reported in:** [Issue #259](https://github.com/bradygaster/squad/issues/259), [Discussion #174](https://github.com/bradygaster/squad/discussions/174) + +VS Code (especially Insiders/Nightly builds) may close without a crash dialog while Squad tasks are executing. This is most often caused by resource pressure — not a bug in Squad itself — but Squad's runtime patterns can contribute to the problem. + +--- + +## Root cause analysis + +An audit of the Squad codebase (SDK and CLI) identified the following resource-pressure vectors: + +### 1. Unbounded in-memory collections (fixed) + +**Streaming usage history** (`streaming.ts`) and **telemetry queue** (`telemetry.ts`) previously grew without limit during long-running sessions. In multi-agent sessions with heavy token throughput, these arrays could accumulate tens of thousands of entries over hours. + +**Fix applied:** Both collections now enforce FIFO eviction caps — 1,000 events for usage history and 500 for telemetry. This bounds worst-case memory contribution to a predictable ceiling. + +### 2. File watcher scope (fixed) + +The `SquadObserver` watches the `.squad/` directory with `recursive: true`. On projects with large orchestration logs, this could generate a high volume of `fs.watch` events. Combined with VS Code's own file watchers on the same workspace, the total watcher count can approach OS limits. + +**Fix applied:** The observer now filters out `orchestration-log/` and `.git/` subdirectories at the watcher callback level, reducing event volume significantly. + +### 3. Stream buffer accumulation (already mitigated) + +The CLI shell accumulates per-agent streaming content in `Map` buffers. The existing `MemoryManager` enforces a 1 MB per-stream cap and trims the active message list to 200 entries. No additional changes needed. + +### 4. Process spawning (no issues found) + +All child processes (`node-pty` for Copilot, `devtunnel`, Docker, .NET) are properly tracked and cleaned up via signal handlers (`SIGINT`, `SIGTERM`). No process leak patterns were detected. + +### 5. Terminal output rate (low risk) + +Squad uses the Ink framework for rendering, which batches React state updates. Direct `process.stdout.write` calls are rate-limited by the model's token generation speed. No fire-hose output patterns were found. + +### 6. Synchronous I/O (startup only) + +`existsSync` and `readFileSync` calls exist in config loading and shell initialization, but these run only at startup — not in hot paths or event loops. + +--- + +## Recommended mitigations + +### For users + +1. **Use VS Code Stable instead of Insiders/Nightly.** Nightly builds may have unresolved memory or renderer bugs that amplify resource pressure from terminal-heavy workloads. + +2. **Increase the file watcher limit** (Linux/macOS): + ```bash + # Check current limit + cat /proc/sys/fs/inotify/max_user_watches + + # Increase temporarily + sudo sysctl fs.inotify.max_user_watches=524288 + + # Increase permanently + echo 'fs.inotify.max_user_watches=524288' | sudo tee -a /etc/sysctl.conf + ``` + +3. **Exclude `.squad/orchestration-log/` from VS Code's file watcher** by adding to `.vscode/settings.json`: + ```json + { + "files.watcherExclude": { + "**/.squad/orchestration-log/**": true + } + } + ``` + +4. **Monitor resource usage** during Squad sessions: + - **Windows:** Task Manager → Details tab, watch `node.exe` memory + - **macOS/Linux:** `top -p $(pgrep -f squad)` or Activity Monitor + - If memory climbs steadily past 1 GB, restart the Squad shell + +5. **Close unused terminals** in VS Code before starting a Squad session. Each terminal consumes renderer memory. + +6. **Disable terminal GPU acceleration** if you see flickering (related: [#254](https://github.com/bradygaster/squad/issues/254)): + ```json + { + "terminal.integrated.gpuAcceleration": "off" + } + ``` + +### For developers + +- When adding new in-memory collections, enforce size caps with FIFO eviction (see `StreamingPipeline.MAX_USAGE_EVENTS` pattern). +- When adding file watchers, always filter high-churn directories in the callback. +- Prefer `async` file I/O in any code path that runs during active sessions. +- Test long-running sessions (2+ hours) to verify memory stays bounded. + +--- + +## Related issues + +| Issue | Description | Status | +|-------|-------------|--------| +| [#259](https://github.com/bradygaster/squad/issues/259) | VS Code crash during Squad execution | This investigation | +| [#254](https://github.com/bradygaster/squad/issues/254) | Terminal flicker in VS Code | Open | +| [Discussion #174](https://github.com/bradygaster/squad/discussions/174) | Original crash report by @diberry | Open | diff --git a/packages/squad-sdk/src/runtime/squad-observer.ts b/packages/squad-sdk/src/runtime/squad-observer.ts index 4ae590e1c..a14cf416d 100644 --- a/packages/squad-sdk/src/runtime/squad-observer.ts +++ b/packages/squad-sdk/src/runtime/squad-observer.ts @@ -121,6 +121,9 @@ export class SquadObserver { try { this.watcher = fs.watch(this.config.squadDir, { recursive: true }, (eventType, filename) => { if (!filename) return; + // Skip high-churn directories that don't affect squad state + const normalized = filename.replace(/\\/g, '/'); + if (normalized.startsWith('orchestration-log/') || normalized.startsWith('.git/')) return; this.handleChange(eventType, filename); }); diff --git a/packages/squad-sdk/src/runtime/streaming.ts b/packages/squad-sdk/src/runtime/streaming.ts index bfd0b3ef4..51e1973ab 100644 --- a/packages/squad-sdk/src/runtime/streaming.ts +++ b/packages/squad-sdk/src/runtime/streaming.ts @@ -94,6 +94,9 @@ export interface AgentUsage { * and usage events. Aggregates token counts across all active sessions. */ export class StreamingPipeline { + /** Cap usage history to prevent unbounded memory growth in long sessions. */ + static readonly MAX_USAGE_EVENTS = 1000; + private deltaHandlers: Set = new Set(); private usageHandlers: Set = new Set(); private reasoningHandlers: Set = new Set(); @@ -178,6 +181,10 @@ export class StreamingPipeline { break; case 'usage': { this.usageData.push(event); + // Evict oldest events when history exceeds cap (FIFO) + if (this.usageData.length > StreamingPipeline.MAX_USAGE_EVENTS) { + this.usageData = this.usageData.slice(-StreamingPipeline.MAX_USAGE_EVENTS); + } await this.dispatchUsage(event); recordTokenUsage(event); // Record response duration and tokens/sec when usage arrives diff --git a/packages/squad-sdk/src/runtime/telemetry.ts b/packages/squad-sdk/src/runtime/telemetry.ts index d31ecdde1..418ae3e60 100644 --- a/packages/squad-sdk/src/runtime/telemetry.ts +++ b/packages/squad-sdk/src/runtime/telemetry.ts @@ -75,6 +75,9 @@ export function setTelemetryTransport(fn: TelemetryTransport): void { * ``` */ export class TelemetryCollector { + /** Cap queued events to prevent unbounded memory growth. */ + static readonly MAX_QUEUE_SIZE = 500; + private queue: TelemetryEvent[] = []; private config: TelemetryConfig; @@ -125,6 +128,11 @@ export class TelemetryCollector { }; this.queue.push(stored); + + // Evict oldest events when queue exceeds cap (FIFO) + if (this.queue.length > TelemetryCollector.MAX_QUEUE_SIZE) { + this.queue = this.queue.slice(-TelemetryCollector.MAX_QUEUE_SIZE); + } } /**