Skip to content

fix(opencode): prevent indexing the entire home directory#1704

Merged
boudra merged 9 commits into
getpaseo:mainfrom
rex-chang:fix/opencode-home-indexing
Jun 26, 2026
Merged

fix(opencode): prevent indexing the entire home directory#1704
boudra merged 9 commits into
getpaseo:mainfrom
rex-chang:fix/opencode-home-indexing

Conversation

@rex-chang

@rex-chang rex-chang commented Jun 24, 2026

Copy link
Copy Markdown
Contributor

Linked issue

None.

Type of change

  • Bug fix
  • New feature (with prior issue + design alignment)
  • Refactor / code improvement
  • Docs

What does this PR do

Paseo no longer starts OpenCode's shared helper server from the user's home directory when there is no explicit project workspace. The helper server uses $PASEO_HOME/opencode-home, a neutral OpenCode workspace, so OpenCode does not treat the real home directory as a project and index the whole tree.

Provider catalog refresh now carries an explicit semantic scope: global refreshes pass { scope: "global" }, while project refreshes pass { scope: "workspace", cwd }. OpenCode maps global catalog refreshes to opencode-home; providers that still need the old behavior map global to their own default themselves. Global snapshots also use a separate internal key, so an explicit home-directory workspace does not reuse the global catalog cache.

The catalog-refresh directory resolution and creation are covered by the server acquisition finally, so filesystem errors cannot leak an acquired OpenCode server reference.

How did you verify it

  • npm install
  • npm run build:client
  • npm run format
  • npm run typecheck
  • npm run lint
  • npx vitest run packages/server/src/server/agent/provider-snapshot-manager.test.ts packages/server/src/server/agent/providers/opencode-agent.test.ts packages/server/src/server/agent/providers/opencode-server-manager.test.ts packages/server/src/server/session/provider/provider-catalog-session.test.ts packages/server/src/server/session.test.ts packages/server/src/server/agent/providers/acp-agent.test.ts packages/server/src/server/agent/providers/pi/agent.test.ts --bail=1 (327 tests passed)

Checklist

  • One focused change. Unrelated cleanups split out.
  • npm run typecheck passes
  • npm run lint passes
  • npm run format ran (Biome)
  • UI changes include screenshots or video for every affected platform (not applicable, no UI changes)
  • Tests added or updated where it made sense

@greptile-apps

greptile-apps Bot commented Jun 24, 2026

Copy link
Copy Markdown

Greptile Summary

This PR fixes a regression where OpenCode would treat the user's home directory as a project workspace and index the entire file tree whenever Paseo launched the shared OpenCode helper server without an explicit project context. The fix has two parts: the helper server now starts in $PASEO_HOME/opencode-home (a neutral directory) instead of os.homedir(), and FetchCatalogOptions gains a discriminated union { scope: "global" } / { scope: "workspace", cwd } so providers can apply their own behavior for global catalog probes without inferring it from a home-directory comparison.

  • Server cwd: OpenCodeServerManager.startServer now resolves and creates $PASEO_HOME/opencode-home via resolveOpenCodeHomeDir, eliminating the direct os.homedir() reference that triggered full-tree indexing.
  • Catalog scope: FetchCatalogOptions is a discriminated union; ProviderSnapshotManager routes no-cwd reads to a dedicated "paseo:global" sentinel key so the global snapshot does not alias any real workspace cwd; ACP and Pi agents fall back to homedir() for global scope by choice, while OpenCode uses its neutral home.
  • Acquisition safety: resolveHomeDir() and fs.mkdir are now both inside the try block that is guarded by finally { acquisition.release() }, closing the ref-count leak paths flagged in earlier review rounds.

Confidence Score: 5/5

Safe to merge. The fix correctly redirects OpenCode's server cwd and catalog directory to a neutral location, and the acquisition ref-count is released in all failure paths via a properly scoped finally block.

The core bug (home-directory indexing) is fixed at both layers — server launch cwd and catalog-fetch directory. The previously flagged acquisition leaks (mkdir failure, resolveHomeDir throwing) are both resolved by moving those calls inside the try block, and new tests exercise both failure paths. The discriminated FetchCatalogOptions union cleanly encodes the intent at the type level, and the global sentinel key keeps the snapshot map from aliasing any real workspace path.

No files require special attention. The redundant resolveSnapshotCwd call inside createWorkspaceSnapshotTarget is harmless and noted inline.

Important Files Changed

Filename Overview
packages/server/src/server/agent/providers/opencode/server-manager.ts Replaces os.homedir() with resolveHomeDir() (→ PASEO_HOME/opencode-home) as the server cwd; adds mkdirSync with recursive: true before spawning. The synchronous mkdir is appropriately placed before any managed-process state is created, so a failure propagates cleanly.
packages/server/src/server/agent/providers/opencode-agent.ts Both this.resolveHomeDir() and fs.mkdir are now inside the try block guarded by finally { acquisition.release() }, closing the ref-count leak paths identified in earlier reviews. Global catalog probes use the neutral opencode-home; workspace probes pass through cwd unchanged.
packages/server/src/server/agent/provider-snapshot-manager.ts Introduces GLOBAL_PROVIDER_SNAPSHOT_KEY = 'paseo:global' as a sentinel map key, ProviderSnapshotTarget carrying snapshotCwd + catalogScope, and resolveProviderSnapshotTarget routing null/empty cwd to the global target. resolveSnapshotCwd is redundantly called inside createWorkspaceSnapshotTarget on an already-resolved path, but this is harmless since the function is idempotent.
packages/server/src/server/agent/agent-sdk-types.ts Converts FetchCatalogOptions from a plain interface to a discriminated union (scope: 'global'
packages/server/src/server/agent/providers/opencode/paths.ts New file exporting resolveOpenCodeHomeDir that computes PASEO_HOME/opencode-home. Correctly colocated with the opencode provider module.
packages/server/src/server/agent/providers/acp-agent.ts ACP's fetchCatalog now resolves cwd from the discriminated union, falling back to homedir() for global scope — consistent with the doc note that providers decide what global means for their own runtime.
packages/server/src/server/session/provider/provider-catalog-session.ts Snapshot-change emission uses isGlobalProviderSnapshotKey to translate the sentinel key back to undefined before emitting to clients; model/mode list requests now use resolveCatalogRequestCwd (returns undefined for empty) instead of resolving to homedir.
packages/server/src/server/agent/providers/opencode-agent.test.ts New tests verify the acquisition-release path when resolveHomeDir throws and when the opencode-home directory cannot be created; existing global-catalog smoke test updated to use neutral opencode-home and assert the correct directory passed to OpenCode.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant C as Client (session)
    participant PSM as ProviderSnapshotManager
    participant OCA as OpenCodeAgentClient
    participant SM as OpenCodeServerManager
    participant FS as FileSystem

    Note over PSM: Global (settings) refresh
    C->>PSM: refreshSettingsSnapshot()
    PSM->>PSM: "createGlobalSnapshotTarget()<br/>snapshotCwd = paseo:global"
    PSM->>OCA: "fetchCatalog({ scope: global, force: true })"
    OCA->>SM: acquireCurrent()
    SM->>SM: resolveHomeDir() → PASEO_HOME/opencode-home
    SM->>FS: mkdirSync(opencode-home)
    SM->>SM: spawn opencode serve --port N in opencode-home
    SM-->>OCA: acquisition
    OCA->>OCA: resolveHomeDir() → opencode-home
    OCA->>FS: "mkdir(opencode-home, { recursive: true })"
    OCA->>SM: "listModels/listModes (directory=opencode-home)"
    OCA->>SM: acquisition.release()
    OCA-->>PSM: ProviderCatalog

    Note over PSM: Workspace refresh
    C->>PSM: "listProviders({ cwd: /project/foo })"
    PSM->>PSM: "createWorkspaceSnapshotTarget(/project/foo)<br/>snapshotCwd = /project/foo"
    PSM->>OCA: "fetchCatalog({ scope: workspace, cwd: /project/foo, force: false })"
    OCA->>SM: acquireCurrent()
    SM-->>OCA: acquisition
    OCA->>OCA: "directory = cwd (no mkdir)"
    OCA->>SM: "listModels/listModes (directory=/project/foo)"
    OCA->>SM: acquisition.release()
    OCA-->>PSM: ProviderCatalog
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant C as Client (session)
    participant PSM as ProviderSnapshotManager
    participant OCA as OpenCodeAgentClient
    participant SM as OpenCodeServerManager
    participant FS as FileSystem

    Note over PSM: Global (settings) refresh
    C->>PSM: refreshSettingsSnapshot()
    PSM->>PSM: "createGlobalSnapshotTarget()<br/>snapshotCwd = paseo:global"
    PSM->>OCA: "fetchCatalog({ scope: global, force: true })"
    OCA->>SM: acquireCurrent()
    SM->>SM: resolveHomeDir() → PASEO_HOME/opencode-home
    SM->>FS: mkdirSync(opencode-home)
    SM->>SM: spawn opencode serve --port N in opencode-home
    SM-->>OCA: acquisition
    OCA->>OCA: resolveHomeDir() → opencode-home
    OCA->>FS: "mkdir(opencode-home, { recursive: true })"
    OCA->>SM: "listModels/listModes (directory=opencode-home)"
    OCA->>SM: acquisition.release()
    OCA-->>PSM: ProviderCatalog

    Note over PSM: Workspace refresh
    C->>PSM: "listProviders({ cwd: /project/foo })"
    PSM->>PSM: "createWorkspaceSnapshotTarget(/project/foo)<br/>snapshotCwd = /project/foo"
    PSM->>OCA: "fetchCatalog({ scope: workspace, cwd: /project/foo, force: false })"
    OCA->>SM: acquireCurrent()
    SM-->>OCA: acquisition
    OCA->>OCA: "directory = cwd (no mkdir)"
    OCA->>SM: "listModels/listModes (directory=/project/foo)"
    OCA->>SM: acquisition.release()
    OCA-->>PSM: ProviderCatalog
Loading

Reviews (9): Last reviewed commit: "fix(opencode): release catalog acquisiti..." | Re-trigger Greptile

Comment thread packages/server/src/server/agent/providers/opencode-agent.ts Outdated
Comment thread packages/server/src/server/agent/providers/opencode-agent.ts Outdated
Comment thread packages/server/src/server/agent/providers/opencode/server-manager.ts Outdated
Paseo launches opencode serve with cwd=os.homedir() and refreshes the
global provider snapshot with directory=/Users/admin. OpenCode treats
that as a workspace and starts location services + bigram indexing for
the entire home tree, causing ~466% CPU and ~4GB RAM usage.

- Use a neutral scratch directory as the opencode serve cwd.
- Use a separate scratch directory for global provider catalog refresh
  so model/mode discovery no longer triggers home directory indexing.

Fixes high CPU/RAM when Paseo starts opencode with no explicit project.
@rex-chang rex-chang force-pushed the fix/opencode-home-indexing branch from d436aec to 98586df Compare June 24, 2026 11:57
rex-chang and others added 4 commits June 24, 2026 20:46
…og refresh

Switch the home-directory check in fetchCatalog from a string-based
path.resolve() comparison to createRealpathAwarePathMatcher, so we
catch macOS /private/var/... aliases, symlinks, trailing separators,
and Windows casing — consistent with the rest of opencode-agent.ts.

Also:
- Hoist the matcher to module scope so each fetchCatalog call doesn't
  rebuild it (the matcher runs realpathSync twice on construction).
- Log a debug line when we rewrite the cwd to the scratch path, so
  it's easy to diagnose missing per-directory config in catalog scope.
- Update opencode-agent.test.ts to expect the scratch directory when
  cwd === os.homedir(), with a comment pointing to the rationale.
Comment thread packages/server/src/server/agent/providers/opencode-agent.ts Outdated
Comment thread packages/server/src/server/agent/providers/opencode-agent.ts Outdated
boudra added 3 commits June 26, 2026 20:16
…e-home-indexing

# Conflicts:
#	packages/server/src/server/agent/agent-manager.ts
#	packages/server/src/server/agent/agent-sdk-types.ts
#	packages/server/src/server/agent/provider-snapshot-manager.ts
#	packages/server/src/server/agent/providers/acp-agent.ts
@boudra boudra merged commit 5ef118e into getpaseo:main Jun 26, 2026
15 of 16 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants