Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
107 changes: 107 additions & 0 deletions docs/src/content/docs/reference/vscode-troubleshooting.md
Original file line number Diff line number Diff line change
@@ -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<string, string>` 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 |
3 changes: 3 additions & 0 deletions packages/squad-sdk/src/runtime/squad-observer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
});

Expand Down
7 changes: 7 additions & 0 deletions packages/squad-sdk/src/runtime/streaming.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<DeltaHandler> = new Set();
private usageHandlers: Set<UsageHandler> = new Set();
private reasoningHandlers: Set<ReasoningHandler> = new Set();
Expand Down Expand Up @@ -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
Expand Down
8 changes: 8 additions & 0 deletions packages/squad-sdk/src/runtime/telemetry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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);
}
}

/**
Expand Down
Loading