Skip to content

[Bug] Isolate PawWork runtime storage from OpenCode namespace #111

@Astro-Han

Description

@Astro-Han

What happened?

PawWork's embedded OpenCode runtime still uses upstream OpenCode namespaces for generated runtime files, caches, config, records, frontend storage keys, desktop sidecar identity, and some broader runtime identity surfaces. This means PawWork can read or write OpenCode global state on a machine that also has OpenCode installed. PawWork should be standalone: users should not need OpenCode installed, and PawWork should not inherit OpenCode model cache, history, auth, config, project records, or other generated global state.

This was first noticed while checking Kimi Coding Plan K2.6 support. PawWork appeared to gain kimi-for-coding/k2p6 because the machine's OpenCode cache at ~/.cache/opencode/models.json had refreshed. A fresh PawWork user without OpenCode should not depend on that external cache.

Steps to reproduce

  1. Inspect packages/opencode/src/global/index.ts: const app = "opencode" is used to derive Global.Path.data, Global.Path.cache, Global.Path.config, and Global.Path.state.
  2. Inspect packages/shared/src/global.ts: the shared global service also derives data, cache, config, state, bin, and log from an opencode app name.
  3. Inspect packages/desktop-electron/src/main/server.ts: the desktop server startup only sets XDG_STATE_HOME to the PawWork userData path, but does not set XDG_DATA_HOME, XDG_CACHE_HOME, or XDG_CONFIG_HOME.
  4. Inspect packages/opencode/src/provider/models.ts: the models cache path is Global.Path.cache/models.json, so it resolves under the OpenCode cache namespace.
  5. Inspect packages/opencode/src/storage/db.ts: the default database path is Global.Path.data/opencode.db, so session records resolve under the OpenCode data namespace.
  6. Inspect packages/desktop-electron/src/main/index.ts: sqliteFileExists() checks XDG_DATA_HOME/opencode/opencode.db, so desktop startup can behave differently on machines with OpenCode data.
  7. Inspect packages/app/src/utils/persist.ts, packages/app/src/entry.tsx, packages/ui/src/theme/context.tsx, and packages/app/public/oc-theme-preload.js: browser persistence keys still use opencode.* and opencode-theme-* namespaces.
  8. Inspect packages/desktop-electron/src/main/server.ts, packages/opencode/src/server/middleware.ts, and packages/desktop-electron/src/main/index.ts: the embedded server still uses OPENCODE_CLIENT, OPENCODE_SERVER_USERNAME, and default username opencode.

Follow-up evidence, not first PR reproduction criteria

  • packages/opencode/src/session/llm.ts, packages/opencode/src/installation/index.ts, and provider setup paths in packages/opencode/src/provider/provider.ts can still expose OpenCode identity through request headers, user agents, and referers.
  • packages/desktop-electron/src/main/index.ts, packages/app/src/pages/layout/deep-links.ts, and related bridge types still use protocol or bridge names such as opencode://, opencode:deep-link, or __OPENCODE__.
  • packages/opencode/src/effect/observability.ts and related OTLP setup still use OpenCode service names or attributes such as opencode.client when telemetry is enabled.

What did you expect to happen?

For the first PR, app-level generated files, local runtime storage, and desktop sidecar internal identity should use PawWork-owned names. External provider request identity, protocol names, bridge names, and observability identity are follow-up work.

PawWork may keep reading project-local OpenCode-compatible files only if that is an explicit compatibility decision, but app-level generated files should not share OpenCode's namespace by default.

First PR: isolate local runtime storage and desktop sidecar identity

Scope

  • Global roots: packages/opencode/src/global/index.ts and packages/shared/src/global.ts still derive global data, cache, config, state, bin, and log from opencode.
  • Desktop server environment: packages/desktop-electron/src/main/server.ts sets only XDG_STATE_HOME; it should isolate XDG_DATA_HOME, XDG_CACHE_HOME, XDG_CONFIG_HOME, and XDG_STATE_HOME for PawWork's embedded server before any import can initialize OpenCode runtime paths.
  • Models, package cache, and downloads: packages/opencode/src/provider/models.ts, packages/opencode/src/npm/index.ts, packages/opencode/src/skill/discovery.ts, and LSP downloads under packages/opencode/src/lsp/server.ts currently depend on Global.Path.cache or Global.Path.bin.
  • Database and records: packages/opencode/src/storage/db.ts, packages/opencode/src/storage/storage.ts, packages/opencode/src/worktree/index.ts, packages/opencode/src/snapshot/index.ts, and packages/opencode/src/tool/truncation-dir.ts currently depend on Global.Path.data and still create OpenCode-named files such as opencode.db.
  • Auth and account state: packages/opencode/src/auth/index.ts, packages/opencode/src/mcp/auth.ts, and packages/opencode/src/account/repo.ts store auth or account data under the OpenCode-derived data root.
  • Config and managed config: packages/opencode/src/config/config.ts, packages/opencode/src/config/paths.ts, and managed config paths still read or write OpenCode global config locations such as /etc/opencode, ProgramData/opencode, and ai.opencode.managed.
  • State, locks, and logs: packages/opencode/src/plugin/meta.ts, packages/opencode/src/provider/provider.ts, packages/shared/src/util/flock.ts, and packages/shared/src/util/effect-flock.ts write plugin metadata, recent model state, locks, or logs under OpenCode-derived roots.
  • Frontend persistence: packages/app/src/utils/persist.ts still uses opencode.global.dat, opencode. prefixes, and opencode.workspace.*.dat; packages/app/src/entry.tsx still uses opencode.settings.dat:defaultServerUrl.
  • Theme and language storage: packages/ui/src/theme/context.tsx and packages/app/public/oc-theme-preload.js still use opencode-theme-*; packages/app/src/context/language.tsx reads opencode.global.dat:language.
  • Desktop store naming: packages/desktop-electron/src/main/constants.ts, packages/desktop-electron/src/main/store.ts, packages/desktop-electron/src/main/ipc.ts, and desktop renderer storage callers still allow or use OpenCode-named stores such as opencode.settings and opencode.global.dat.
  • Desktop sidecar identity: packages/desktop-electron/src/main/server.ts, packages/desktop-electron/src/main/index.ts, packages/opencode/src/server/middleware.ts, and desktop renderer health checks still use OPENCODE_CLIENT, OPENCODE_SERVER_USERNAME, or default Basic Auth username opencode.

Acceptance criteria

  • PawWork desktop sets the data, cache, config, and state root inputs to PawWork-owned locations before any import that can initialize Global.Path.* or read OpenCode runtime paths. Derived Global.Path.bin and Global.Path.log must also resolve under PawWork-owned locations. Static top-level imports that initialize the embedded server before environment setup should be avoided or fail a regression test.
  • PawWork-owned generated names include, at minimum, a PawWork-owned DB filename such as pawwork.db, PawWork-owned Electron store names, and PawWork-owned frontend keys replacing opencode.global.dat, opencode.workspace.*.dat, opencode.settings.dat:defaultServerUrl, opencode-theme-*, and related language/theme persistence keys.
  • The first PR should not introduce new app-level generated opencode filenames except where explicitly documented as project-local compatibility.
  • Global.Path.bin and Global.Path.log resolve to PawWork-owned locations for the desktop runtime.
  • The default sidecar client identity and Basic Auth username use PawWork-owned values, and desktop health checks use the same PawWork-owned username.
  • Existing internal environment variable key names may remain if changing them would broaden the PR. Changing key names is only in scope when contained to the desktop sidecar path and covered by tests.
  • Project-local OpenCode-compatible config may remain readable. PawWork must not implicitly read global OpenCode user, system, or managed config locations in the first PR, including OpenCode user config roots, home-level .opencode discovery, /etc/opencode, ProgramData/opencode, and ai.opencode.managed. Explicit opt-in paths such as an intentionally supplied config directory may remain only if documented and tested as opt-in compatibility.
  • PawWork-generated app-level or global config should write pawwork.json or pawwork.jsonc. Reading opencode.json or opencode.jsonc under the PawWork-owned config root is either prohibited, or explicitly documented and tested as an app-level compatibility exception.
  • OpenCode Zen and OpenCode Go remain callable through the existing provider logic.

First PR compatibility policy for project-local OpenCode names

  • Project-local OpenCode-compatible config may remain readable as explicit compatibility. PawWork-generated project config should prefer pawwork.json and pawwork.jsonc.
  • Project-local .opencode directories outside TUI-only code may remain readable only as documented compatibility. The first PR should not create new .opencode project-local generated state unless the specific path is documented and tested as a compatibility exception.
  • Project .git/opencode is a project-local compatibility exception for the first PR and may remain unchanged. If the first PR changes it, it must explicitly implement and test the chosen .git/pawwork or dual-read policy.
  • Config schema URLs and managed config names are not required to be fully renamed in the first PR, except that PawWork must not implicitly read global OpenCode managed config locations by default.
  • PawWork-generated app-level or global config should write pawwork.json or pawwork.jsonc. Reading opencode.json or opencode.jsonc under the PawWork-owned config root is either prohibited, or explicitly documented and tested as an app-level compatibility exception.

Deferred because #103 removes TUI

  • TUI-only tips, TUI-only theme names, TUI-only error links, TUI clipboard temp names, and TUI plugin paths should not block this issue while chore: remove unused TUI surface #103 is pending. After chore: remove unused TUI surface #103 merges, re-scan the remaining non-TUI runtime paths and only keep residual findings that still affect PawWork runtime storage, network identity, or user-visible app integration.

Follow-up scope: external identity and protocol namespaces

  • Network identity sent outside the app: packages/opencode/src/session/llm.ts can send x-opencode-* headers for OpenCode-backed providers, non-OpenCode provider paths can still send User-Agent: opencode/..., and provider setup can still send HTTP-Referer: https://opencode.ai/ to third-party providers.
  • Observability identity: packages/opencode/src/effect/observability.ts and related OTLP setup still use OpenCode service names or attributes such as opencode.client when telemetry is enabled.
  • Runtime protocol namespace: packages/desktop-electron/src/main/index.ts, packages/app/src/pages/layout/deep-links.ts, and related bridge types still use opencode://, opencode:deep-link, or __OPENCODE__.

Not in scope for the first PR unless separately approved

  • Renaming workspace package scopes such as @opencode-ai/app, @opencode-ai/ui, @opencode-ai/sdk, or the vendored packages/opencode directory.
  • Renaming provider IDs or product names for OpenCode Zen / OpenCode Go when those are actual provider products.
  • Changing external provider request headers, User-Agent, Referer, or OpenCode provider network identity.
  • Changing URL schemes, deep-link protocol names, renderer bridge names, or observability identity.
  • Cleaning Storybook, playground, test-only temporary path names, Vite plugin names, DOM IDs, CSS highlight names, or development script logs that do not affect user runtime storage, external network identity, or global app integration.
  • Broad brand-copy cleanup unrelated to generated files, runtime persistence, external request identity, or global namespace isolation.
  • Automatically migrating or fallback-reading global OpenCode user data.

PawWork version

Current dev branch, observed at 63a8a52726d9d4f72c566a09802fedac20b1e2a3.

OS version

macOS, observed on the local development machine. This also matters for Windows because Electron userData, ProgramData, installer state, and shell environment behavior differ across platforms.

Can you reproduce it again?

Yes, every time.

Implementation Spec

Decision

This issue should be fixed with a PawWork runtime namespace boundary, not a broad OpenCode rewrite.

PawWork should continue using OpenCode as the underlying engine and should continue supporting OpenCode Zen and OpenCode Go provider access. This work must not rename those provider products, remove their connection paths, or change the provider runtime logic required to call them.

App-level generated runtime state that PawWork reads or writes by default should belong to PawWork. Project-local OpenCode-compatible config may remain readable only through the explicit compatibility policy above.

Scope for the first implementation PR

The first PR should fix local runtime storage and desktop sidecar identity only.

In scope:

  • Isolate embedded server roots for data, cache, config, state, bin, and log under PawWork-owned app data before the server module is imported.
  • Move PawWork-generated cache, config, state, database, logs, package cache, model cache, account state, auth state, locks, and related runtime files into PawWork-owned paths.
  • Rename PawWork-owned generated database and persistence filenames where they are part of user runtime state, for example replacing OpenCode-owned names with PawWork-owned names.
  • Rename Electron store names from OpenCode-owned names to PawWork-owned names.
  • Rename frontend persistence keys from opencode.global.dat and opencode.workspace.*.dat to PawWork-owned keys.
  • Rename theme, language, terminal debug, and default server URL persistence keys that currently point at OpenCode-owned storage.
  • Change desktop embedded server internal identity values to PawWork, including default server username, sidecar client identity, and matching health-check Basic Auth username. Internal environment variable key names may stay as-is if renaming them would broaden the PR.
  • Keep project-level config compatibility intentional: pawwork.json should be preferred, and existing OpenCode-compatible project config can remain readable only as a compatibility policy, not as accidental global state sharing.

Out of scope for the first PR:

  • Do not change OpenCode Zen or OpenCode Go provider IDs, names, endpoints, or connection behavior.
  • Do not change external provider request headers, User-Agent, Referer, or OpenCode provider network identity in this PR.
  • Do not change URL schemes, deep-link protocol names, renderer bridge names, or observability identity in this PR unless required by local storage isolation.
  • Do not change TUI-only paths because PR chore: remove unused TUI surface #103 is expected to remove the TUI surface.
  • Do not automatically migrate or fallback-read global OpenCode user data.
  • Do not do a broad string replacement across packages/opencode, package scopes, tests, Storybook, or development-only names.

Expected runtime flow

  1. PawWork desktop computes its PawWork app data root.
  2. Before importing the embedded server, PawWork sets the process environment so OpenCode runtime paths resolve under PawWork-owned roots.
  3. The embedded OpenCode runtime keeps using its normal provider/session/model logic, but its filesystem roots now point at PawWork storage.
  4. Electron main process stores PawWork settings in PawWork-owned store names.
  5. Renderer persistence writes PawWork-owned keys and does not fallback to OpenCode-owned localStorage or Electron stores.
  6. OpenCode Zen and OpenCode Go remain callable through the existing provider logic.

Testing requirements

  • Add or update tests proving PawWork does not read or write global OpenCode data/cache/config/state/bin/log or any derived package, model, LSP, lock, auth, account, database, and log paths by default.
  • Add or update tests for the desktop server environment showing all relevant roots are set before embedded server import and before any import that can initialize Global.Path.*.
  • Add or update tests for frontend persistence key generation, including global and workspace keys.
  • Add or update tests for Electron store naming and sidecar health-check auth username.
  • Add a smoke-level verification that provider discovery still exposes OpenCode Zen and OpenCode Go after namespace isolation.
  • Tests may use mocked platform path resolution, but they should cover the Windows path policy enough to prove PawWork does not resolve to OpenCode global data/cache/config/state/bin/log names on Windows.

Follow-up work

After this first PR, separately evaluate external network identity and protocol namespaces:

  • x-opencode-* headers
  • User-Agent: opencode/...
  • HTTP-Referer: https://opencode.ai/
  • opencode://, opencode:deep-link, and __OPENCODE__
  • observability service names and attributes

Those are real issues, but they are deliberately split from the first implementation PR to keep provider compatibility and local storage isolation easier to verify.

Metadata

Metadata

Assignees

No one assigned

    Labels

    P1High prioritybugSomething isn't workingplatformElectron shell, OS integration, packaging, updater, signing, paths, and permissions

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions