fix(config): catch parse errors gracefully during startup#3
Conversation
…uation (anomalyco#26178) Co-authored-by: Aiden Cline <aidenpcline@gmail.com>
Wrap ConfigParse.jsonc() and ConfigParse.effectSchema() calls in
loadConfig with Effect.catchCause to prevent sync-thrown JsonError
and InvalidError from becoming Effect defects. On parse/schema
failure, the bad file is skipped with a log.error and {} fallback
instead of crashing, matching the existing tui.jsonc error handling
pattern in tui.ts.
Without this, any invalid JSONC syntax or schema violation in
opencode.json/opencode.jsonc causes "4 of 6 requests failed:
Unexpected server error" on startup. Now the server starts with
default values and logs the config error details.
Fixes anomalyco#29200
|
This PR doesn't fully meet our contributing guidelines and PR template. What needs to be fixed:
Please edit this PR description to address the above within 2 hours, or it will be automatically closed. If you believe this was flagged incorrectly, please let a maintainer know. |
|
@gemini-code-review pls review |
There was a problem hiding this comment.
Code Review
This pull request refactors the application's sync and SDK layers, transitioning "Global" contexts to "Server" and introducing directory-specific synchronization. The desktop build system is migrated from Tauri to Electron, and the session timeline is updated with virtualization. A new "v2" UI design for the prompt input and titlebar is introduced for non-production environments. Additionally, a referral system is added to the console, infrastructure monitoring is updated, and Ukrainian translations are provided. Feedback identifies a missing luxon import and a logic error in directory matching. Recommendations are also made to improve the virtualized timeline's row preservation, accessibility for screen readers, and state management when navigating between sessions.
| } | ||
|
|
||
| function groupSessions(records: HomeSessionRecord[], language: ReturnType<typeof useLanguage>): HomeSessionGroup[] { | ||
| const now = DateTime.local() |
|
|
||
| const current = createMemo(() => serverSync.child(directory)) | ||
| const target = (directory?: string) => { | ||
| if (!directory || directory === directory) return current() |
There was a problem hiding this comment.
The condition directory === directory is a tautology because the parameter directory shadows the outer scope variable of the same name. This makes the subsequent code on line 178 unreachable. You should rename the parameter or use a different check to determine if the requested directory matches the current one.
if (!directory || directory === current()[0].path.directory) return current()| const keepMounted = createMemo(() => { | ||
| const id = activeMessageID() | ||
| if (!id) return | ||
| const rows = timelineRows() | ||
| const index = rows.findLastIndex((row) => "userMessageID" in row && row.userMessageID === id) | ||
| if (index < 0) return | ||
| return [index] | ||
| }) |
There was a problem hiding this comment.
The keepMounted logic currently only preserves the last row of the active turn. Since a turn can consist of multiple rows (UserMessage, multiple AssistantParts, Thinking, etc.), scrolling up while a turn is active might cause earlier parts of the streaming response to be unmounted by the virtualizer. This can disrupt the streaming experience and stateful animations like TextReveal. It is recommended to keep all rows associated with the active userMessageID mounted.
const keepMounted = createMemo(() => {
const id = activeMessageID()
if (!id) return
const rows = timelineRows()
const indices: number[] = []
rows.forEach((row, index) => {
if ("userMessageID" in row && row.userMessageID === id) {
indices.push(index)
}
})
return indices.length > 0 ? indices : undefined
})
| }} | ||
| data-slot="session-turn-assistant-content" | ||
| aria-hidden={workingTurn(assistantPartRow().userMessageID)} | ||
| > |
There was a problem hiding this comment.
| const [bar, setBar] = createStore({ | ||
| ms: pace(640), | ||
| }) | ||
| const [toolOpen, setToolOpen] = createStore<Record<string, boolean | undefined>>({}) |
There was a problem hiding this comment.
The toolOpen store persists state for the lifetime of the MessageTimeline component. When switching between sessions, this state is not cleared, which could lead to incorrect expanded/collapsed states if part IDs happen to collide or simply unnecessary memory growth. Consider clearing this store when the sessionKey changes.
Closes anomalyco#29200
Type of change
What does this PR do?
Invalid JSON or JSONC in
opencode.jsoncproduces "Unexpected server error" on startup.ConfigParse.jsonc()throwsJsonErrorandConfigParse.schema()throwsInvalidErroras plain exceptions, which become Effect defects insideEffect.genand propagate as generic HTTP 500s.Wraps the parsing/schema-validation steps in
Effect.syncwithEffect.catchCause(the correct operator for catching sync-thrown defects insideEffect.gen). On error, logs the cause server-side and returns an empty{}config viaSchema.decodeSync, so the server starts with defaults.Plugin resolution errors are caught per-file (log + continue) so one bad file does not collapse all global config loading.
How did you verify your code works?
Related