Skip to content

fix(desktop): prevent background service SIGKILL loops and tune SQLite WAL concurrency - #53387

Closed
4Liberty wants to merge 2 commits into
anomalyco:devfrom
4Liberty:fix/supervisor-watchdog-and-sqlite-tuning
Closed

4Liberty wants to merge 2 commits into
anomalyco:devfrom
4Liberty:fix/supervisor-watchdog-and-sqlite-tuning

Conversation

@4Liberty

@4Liberty 4Liberty commented Oct 5, 2026 •

Copy link
Copy Markdown

Issue for this PR

Closes #51216

Type of change

  • Bug fix
  • New feature
  • Refactor / code improvement
  • Documentation

What does this PR do?

On Windows (and systems with high I/O or database load), the desktop app frequently kills its own background service (\opencode-cli) while the service is busy handling transactions or checkpoints, causing a continuous ~60s restart loop, aborted turns (\HTTP 499), and process contention.

Root Causes Identified:

  1. Aggressive Supervisor Timeouts: \defaultEnsureTiming\ configured
    equestTimeout: 2_000\ (2s) and evicted the registered service after 3 consecutive timeouts. A database transaction, WAL checkpoint, or git snapshot blocking the HTTP event loop for just 6.1s caused the supervisor to declare the service unresponsive, SIGTERM/SIGKILL the active PID, delete \service.json, and trigger a restart storm.
  2. Contender Race Condition: When
    egistration.service === undefined\ (e.g. while the server is starting or momentarily saturated), \pool.shouldRecruit(true)\ spawned a contender child process after \spawnDelay: 5_000. Even though the incumbent PID was running in the OS process table, contenders were launched concurrently, racing for database locks and overwriting \service.json.
  3. SQLite Default Synchronous Mode: In WAL mode, SQLite defaults to \PRAGMA synchronous = FULL\ on each new connection unless explicitly configured. With large databases or multi-session loads, \FULL\ forces synchronous disk flushes per transaction commit, inducing severe I/O stalls and lock contention.
  4. Premature SSE Stream Drops & Sidecar Reconnect Churn: In Solid \createClientConnection, \ oregroundIdleThreshold\ was set to 20s and \defaultIdleTimeout\ to 45s. Switching windows or backgrounding the desktop app frequently tripped this threshold, tearing down the stream. The desktop renderer's \createSidecarResolver\ then called
    econnectService()\ IPC unconditionally, re-entering \Service.ensure()\ and accelerating supervisor churn even though the backend process was healthy.

Changes Made:

  • \packages/core\ (\database.ts, \sqlite.bun.ts\ & \sqlite.node.ts):
    • Configured \PRAGMA synchronous = NORMAL;\ and raised \PRAGMA busy_timeout = 30000;\ (30s) on non-readonly database connections. As documented in SQLite's WAL design, \NORMAL\ is fully crash-resistant in WAL mode and avoids \ sync\ lock stalls.
  • \packages/desktop\ (\main/storage/database.ts,
    enderer/startup/initialization.ts)
    :
    • Added \PRAGMA busy_timeout = 30000;\ to desktop SQLite storage initialization to prevent transient lock failures.
    • In \createSidecarResolver, probe /api/info\ on the current sidecar URL before invoking
      econnectService()\ IPC. If the incumbent server is responsive, reuse it immediately without triggering supervisor re-checks.
  • \packages/client\ (\service-timing.ts, \promise/service.ts, \�ffect/service.ts, \solid/connection.ts):
    • Increased
      equestTimeout\ (2s -> 10s), \spawnDelay\ (5s -> 15s), \maxSpawnDelay\ (30s -> 60s), and \promiseTimeout\ (120s -> 180s) in \defaultEnsureTiming.
    • Raised supervisor eviction threshold from 3 to 10 consecutive timeouts (\ imeouts.count >= 10), allowing busy background processes adequate headroom before eviction.
    • Guarded contender recruitment in both Promise and Effect service loops: contenders are never spawned when the incumbent PID is confirmed alive in the OS process table (!stopped(pid)\ / \isPidAlive(pid)).
    • Relaxed SSE \defaultIdleTimeout\ to 120s and \ oregroundIdleThreshold\ to 300s (5m) to avoid spurious reconnections on window switches.
  • \packages/app\ (
    untime/server/request-queue.ts)
    :
    • Raised
      equestStallMs\ (2s -> 10s) to prevent false-positive thrashing warnings during initial session loading.

How did you verify your code works?

  • Validated with unit tests in \packages/client\ and \packages/core:
    • \�un test test/service-contender-pool.test.ts\ passes (6 pass, 0 fail).
    • \�un run --filter @opencode/client typecheck\ and \�un run --filter @opencode/core typecheck\ exit code 0.
  • Verified live on Windows 11 with a high-volume SQLite database (500k+ messages):
    • Service probe latency dropped from >2.5s / timeout down to ~50ms.
    • Zero background service kills, zero contender collisions, and zero spurious restart loops over sustained multi-hour sessions.

Screenshots / recordings

Not a UI change.

Checklist

  • I have tested my changes locally
  • I have not included unrelated changes in this PR

…timize SQLite concurrency

- packages/core: Configure PRAGMA synchronous = NORMAL and busy_timeout = 10000 on writable SQLite connections. In WAL mode, SQLite defaults to synchronous = FULL, which causes fsync lock stalls on larger journals. NORMAL is crash-resilient with WAL and prevents query lock timeouts.
- packages/client:
  - Increase service probe requestTimeout (2s -> 10s), spawnDelay (5s -> 15s), maxSpawnDelay (30s -> 60s), and promiseTimeout (120s -> 180s) to give busy background servers adequate time to complete queries.
  - Guard contender recruitment in both Promise and Effect service loops by verifying whether the incumbent PID is alive in the OS process table. Contenders are no longer spawned concurrently while an incumbent server process is running.
  - Relax event stream defaultIdleTimeout (45s -> 120s) and foregroundIdleThreshold (20s -> 300s) to eliminate false-positive stream drops when switching windows.
- packages/desktop: In createSidecarResolver, probe the existing sidecar URL before invoking reconnectService IPC, preventing unnecessary supervisor churn on transient UI stream reconnections.
- packages/app: Increase requestStallMs (2s -> 10s) to prevent spurious thrashing warnings during bursty initial session loading.
@github-actions

github-actions Bot commented Oct 5, 2026

Copy link
Copy Markdown
Contributor

Hey! Your PR title fix(desktop, client, core): prevent background service SIGKILL loops and tune SQLite WAL concurrency doesn't follow conventional commit format.

Please update it to start with one of:

  • feat: or feat(scope): new feature
  • fix: or fix(scope): bug fix
  • docs: or docs(scope): documentation changes
  • chore: or chore(scope): maintenance tasks
  • refactor: or refactor(scope): code refactoring
  • test: or test(scope): adding or updating tests

Where scope is the package name (e.g., app, desktop, opencode).

See CONTRIBUTING.md for details.

@github-actions

github-actions Bot commented Oct 5, 2026

Copy link
Copy Markdown
Contributor

The following comment was made by an LLM, it may be inaccurate:

@4Liberty 4Liberty changed the title fix(desktop, client, core): prevent background service SIGKILL loops and tune SQLite WAL concurrency fix(desktop): prevent background service SIGKILL loops and tune SQLite WAL concurrency Oct 8, 2026
@github-actions

github-actions Bot commented Oct 8, 2026

Copy link
Copy Markdown
Contributor

Thanks for your contribution!

This PR doesn't have a linked issue. All PRs must reference an existing issue.

Please:

  1. Open an issue describing the bug/feature (if one doesn't exist)
  2. Add Fixes #<number> or Closes #<number> to this PR description

See CONTRIBUTING.md for details.

@github-actions

github-actions Bot commented Nov 5, 2026

Copy link
Copy Markdown
Contributor

Automated PR Cleanup

Thank you for contributing to opencode.

Due to the high volume of PRs from users and AI agents, we periodically close older PRs using automated criteria so maintainers can focus review time on the most active and community-supported contributions.

This PR was closed because it matched the following cleanup criteria:

  • The PR was created more than 1 month ago
  • The PR had fewer than 2 positive reactions
  • Positive reactions are counted as thumbs-up, heart, celebration, or rocket reactions on the PR

PRs created within the last month are not affected by this cleanup.

If you believe this PR was closed incorrectly, or if you are still actively working on it, please leave a comment explaining why it should be reopened. A maintainer can review and reopen it if appropriate.

Thanks again for taking the time to contribute.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

desktop(win): background service SIGKILLed mid-turn when snapshot capture blocks /api/info for >6s -> ~60s restart loop, aborted turns

1 participant