anomalyco / anomalyco/opencode
Desktop renderer crashes: Solid store proxy Object.keys race + fatal 404/permission-server handling
@Hona ci sta già lavorando.
Dal 30/8/2026.
- Lingua principale
- TypeScript
- Stelle
- 209k
- Fork
- 27.5k
- Merge medio
- 7h 2m
- PR unite (30g)
- 384
Descrizione
Summary
OpenCode desktop (v1.18.25, Windows, packaged app) crashes repeatedly at startup. The crashes are fatal renderer errors — the renderer dies, taking the app down. Three distinct failure modes observed, all renderer-side:
- Solid.js store proxy enumeration race —
Object.keys()on thesession_statusstore proxy throws V8 proxy-invariant errors. - "Session not found" 404 treated as fatal — resolving/fetching messages for a session that no longer exists kills the renderer.
- "Permission server not found" treated as fatal — when the active server key is missing from the servers list,
ensure()throws inside a memo during render.
All three are fatal renderer error entries in %APPDATA%\ai.opencode.desktop\logs\<session>\renderer.log followed by render process gone.
Bug 1: Object.keys() on Solid store proxy throws (primary crash, every startup)
Stack (identical code site across runs; V8 error message varies run to run):
TypeError: Invalid property descriptor. Cannot both specify accessors and a value or writable attribute, #<Object>
at Object.keys (<anonymous>)
at oc://renderer/assets/main-8t-QUXax.js:104444:19
at Array.some (<anonymous>)
at Object.fn (oc://renderer/assets/main-8t-QUXax.js:104443:45)
at runComputation ...
at batch ...
at Object.setStore [as set] (oc://renderer/assets/main-8t-QUXax.js:34536:5)
at oc://renderer/assets/main-8t-QUXax.js:61988:28
at async retry (...)
at async Promise.allSettled (index 3)
Variants seen on the same line: Getter must be a function: <number> and 'getOwnPropertyDescriptor' on proxy: trap reported non-configurability for property '...' which is either non-existent or configurable in the proxy target.
The code site (renderer bundle, main-*.js): an isWorking memo in the projects/home view:
const isWorking = createMemo(() => dirs().some((directory) => {
return Object.keys(serverSync().session.data.session_status).some((id) => {
if (serverSync().session.get(id)?.directory !== directory) return false;
return serverSync().session.data.session_working(id);
});
}));
Trigger: at bootstrap, the session_status store is written from two concurrent paths — the bootstrap slow-list (index 3 of Promise.allSettled: session.set("session_status", sessionID, reconcile(status))) and loadActiveSessionsQuery/seedActiveSessionStatuses — plus session events. The memo re-runs on every store update and enumerates the proxy while writers mutate the target → V8 proxy invariant violation → TypeError → fatal.
Amplifier: with a large session count (448 sessions in the shared opencode.db), the enumeration is large and the race hits on essentially every startup. It is timing/state dependent — the app can run fine for hours and then crash when the status map is written while the memo enumerates.
Mitigation that works (local patch): make the memo enumerate a stable snapshot instead of the live proxy:
const isWorking = createMemo(() => {
let keys = [];
try { keys = Object.keys({ ...serverSync().session.data.session_status }); } catch (e) { keys = []; }
return dirs().some((directory) => keys.some((id) => {
try {
if (serverSync().session.get(id)?.directory !== directory) return false;
return serverSync().session.data.session_working(id);
} catch (e) { return false; }
}));
});
(Note: spreading first — {...proxy} — avoids the getOwnPropertyDescriptor trap entirely.)
Bug 2: "Session not found" 404 is a fatal renderer error
Error: Session not found: ses_<id>
at wrapClientError (main-8t-QUXax.js:57589:12)
at request (main-8t-QUXax.js:52505:28)
at async retry (...)
at async fetchMessages (...)
at async loadMessages (...)
at async Promise.all (index 1)
Caused by: { "body": { "name": "NotFoundError", "data": { "message": "Session not found: ..." } }, "status": 404 }
The renderer resolves session IDs that appear in session status / lists but no longer exist server-side (stale local cache, deleted sessions, DB inconsistency between list and get). session.resolve() / fetchMessages() reject with a 404 and the rejection reaches the global handler → fatal renderer error.
Sessions in this crash were not present in the server's DB (SELECT ... FROM session returned nothing) but were still referenced by the app's persisted state (Local Storage / workspace state files). A deleted/stale session should never take down the UI.
Mitigation that works (local patch): resolve2 in the session store catches not-found errors and resolves null:
const request = (sessionApi ? sessionApi.get({ sessionID }).then(normalizeSessionInfo) : client2.session.get({ sessionID }).then((result) => {
if (!result.data) throw sessionNotFoundError(sessionID);
return result.data;
})).catch((err) => {
if (isLocalSessionNotFoundError(err, sessionID) || isSessionNotFoundError(err, sessionID)) return null;
const body = err?.body;
if (body?.status === 404 || err?.status === 404 || body?.name === "NotFoundError") return null;
throw err;
});
const resolved = request.then((result) => {
if (result == null) return null;
...
});
Bug 3: "Permission server not found" is a fatal renderer error
Error: Permission server not found: http://100.126.12.120:8081
at ensure (main-8t-QUXax.js:77458:24)
at selected (main-8t-QUXax.js:77501:14)
at Object.fn (main-8t-QUXax.js:77520:14)
at runComputation ... (Solid memo during render)
When the app's active server (server.key, persisted from a previous session) is not present in global2.servers.list() (e.g., server config was cleared/updated, or a remote server became stale), ensure() throws inside a createMemo during render → fatal. This happens on startup when a remote server (Tailscale URL like http://100.126.12.120:8081) was the last active server but is not in the configured list.
The throw site should be defensive — a missing active server should fall back gracefully (e.g., to the local sidecar) rather than crash the renderer.
Environment
- OpenCode desktop 1.18.25 (latest at time of writing), Windows 10/11, packaged (Electron + custom
oc://protocol) - Large shared storage: 448 sessions / 16.7k messages / 71.5k parts in
opencode.db - Multiple remote servers configured (Tailscale/LAN
http://host:8081), one of which was unreachable at crash time - Crash since v1.18.25; reproduced on every startup when session state is large and/or a stale remote server is the active one
Suggested fixes
- Do not run
Object.keys()/Object.entries()directly on Solid store proxies that are being concurrently written; enumerate snapshots, or harden the store'sgetOwnPropertyDescriptortrap against V8 proxy invariant violations under concurrent mutation. - Treat
404 / Session not foundandPermission server not foundas recoverable conditions — never fatal. Fall back (skip the session; use the local server) instead of letting the rejection reach the global error handler. - Avoid restoring stale session references from persisted state (Local Storage / workspace caches) — validate against the server before use.
Happy to provide full log bundles / crashpad dumps if useful.
Guida per i contributori
Apri la guida per i contributori
Come iniziare
- Leggi tutta la issue e poi la guida ai contributi del progetto.
- Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
- Fai un fork del repository e lavora su un branch.
- Apri una pull request che faccia riferimento al numero della issue.
Valutazione
Questa issue non è ancora stata valutata.