fix(opencode): prevent indexing the entire home directory#1704
Conversation
|
| 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
%%{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
Reviews (9): Last reviewed commit: "fix(opencode): release catalog acquisiti..." | Re-trigger Greptile
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.
d436aec to
98586df
Compare
…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.
…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
Linked issue
None.
Type of change
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 toopencode-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 installnpm run build:clientnpm run formatnpm run typechecknpm run lintnpx 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
npm run typecheckpassesnpm run lintpassesnpm run formatran (Biome)