Skip to content

fix(tui): one malformed custom theme file rejects all theme discovery — every valid custom theme is lost and the main TUI resets the active theme #1683

Closed

Description

@dezren39

Problem

One malformed (or unreadable) custom theme file rejects the entire custom-theme discovery — every valid custom theme in every config directory is lost for the session.

packages/tui/src/theme/discovery.ts:17 (on main 618de2888fcf):

result[path.basename(file, ".json")] = JSON.parse(await readFile(file, "utf8")) as unknown

There is no per-file error isolation: readFile failures and JSON.parse failures propagate out of the sequential loops in discoverThemes (only the readdir at :8-11 is guarded, and only for ENOENT).

Verified reproduction (laptop probe, origin/main 618de28, run twice with identical results)

Probe importing discoverThemes verbatim from the main tree, with two directories each holding one valid theme:

  • Control (all valid): resolves {"good-a":{"source":"a"},"good-b":{"source":"b"}}
  • After adding one file a/themes/broken.json containing { not json: REJECTED — SyntaxError: JSON Parse error: Expected '}' at discovery.ts:17. Neither valid theme is returned, including the one in the untouched second directory.

Why it matters — both callers degrade badly

  • Main TUI (packages/tui/src/context/theme.tsx:160-166): syncCustomThemes() catches the rejection with .catch(() => setStore("active", "opencode")) — one bad file not only drops all custom themes, it silently overrides the user's configured theme selection back to the built-in opencode theme.
  • Mini TUI (packages/tui/src/mini/theme.ts:312): themeSource() awaits discoverThemes(...) with no local guard; the rejection propagates to the caller's fallback, again losing every custom theme (including the requested one, even when the requested theme's own file is perfectly valid).

Custom themes are user-owned files in themes/ directories (global config + project .opencode/); a typo in one JSON file — the most likely state of a theme a user is actively editing — should cost that one theme, not all of them plus the user's selection. Sibling user-state parsers in the same package already isolate per-entry failures (e.g. parsePromptStash in packages/tui/src/prompt/stash.tsx skips bad lines; parsePromptInfo in history.tsx validates shape).

Acceptance criteria

  • discoverThemes resolves (does not reject) when an individual theme file is malformed JSON or unreadable; that file is skipped and all other valid themes, in all directories, are still returned with the existing directory-precedence behavior intact.
  • The skipped file's failure is diagnosable (named in a warning/log or otherwise surfaced — not silently swallowed without trace, if a suitable channel exists in the TUI; at minimum the behavior is pinned by tests).
  • New tests in packages/tui/test/theme.test.ts next to the existing discoverThemes precedence tests (~:126-150): malformed file in the first directory does not lose valid themes from either directory; malformed file that would shadow a valid same-named theme does not take the valid one down with it (decide and pin whether the earlier valid entry survives).
  • Existing theme tests pass.

Oracle: the existing discoverThemes precedence tests plus per-entry isolation precedent in the sibling TUI parsers (stash.tsx/history.tsx).

Activity

  1. added
    kind/bugSomething isn't working (prefixed form of the classic bug label)
    kind/testTest coverage, test infrastructure, or test-only fixes
    task/small-fixQueue packet kind: bounded fix with reproduction and targeted tests
    source/creatorFiled by the new-issue-creator job
    priority/normalOrdinary priority (the default)
    queue/queuedHas a queued task in the Supabase agent queue
    app/tuiPackage: tui (pkgs/ or apps/ in this repo)
    effort/sSmall: expected under 2 hours of work
    on Nov 5, 2026
  2. dezren39 commented on Nov 5, 2026

    @dezren39
    Author

    🤖 Agent note · state: filed

    • Agent: new-issue-creator-opencode (issue creator, Make fleet)
    • Session / ID: cron new-issue-creator-opencode, run 2026-11-04 19:35 CST
    • Found via: filed this run
    • Task: Filed this issue after verifying on origin/main 618de2888fcf6e3eae88ffae3c97923a4b7e907d in a laptop probe (~/git/fleet/probe-2026-11-04-creator-opencode-1935, probe script in probe-2026-11-04-creator-opencode-1935b, run twice with identical results): control with two valid themes resolves both; adding one broken.json ({ not json) in the first directory REJECTS the whole discoverThemes call with a SyntaxError at discovery.ts:17, losing the valid theme in the untouched second directory too. A repo-task/v1 small-fix packet is enqueued as task 9797 (queue repo-health-opencode-pilot, dedupe repo-health:v1:fix:developing-today/opencode:issue:1683 — the queue validator still requires the pre-move repo name in packets; packet file hidden_files/packet-opencode-1683.json).
    • Expectation: effort/s — per-file isolation in discoverThemes (skip the bad file, keep every other valid theme and the directory precedence), with regression tests in packages/tui/test/theme.test.ts; done when one malformed theme file no longer loses all custom themes or resets the active theme.
    • Links: evidence file hidden_files/issue-opencode-theme-discovery-malformed.md (goal workspace); sibling per-entry isolation precedent: packages/tui/src/prompt/stash.tsx / history.tsx
  3. added
    work/assignedAn agent holds this item via the queue (claimed task)
    queue/claimedA queue worker has claimed the task for this item
    and removed
    queue/queuedHas a queued task in the Supabase agent queue
    on Nov 5, 2026
  4. dezren39 commented on Nov 5, 2026

    @dezren39
    Author

    Task 9797 picked up by repo-worker-opencode on lane 3fe37e8a-c61b-419c-9e97-769ecd264d5d. Fresh GitHub checks confirm #1683 remains open and there is no matching PR. I will reproduce and fix per-file failures in discoverThemes, then add targeted theme tests.

  5. added
    queue/parkedQueue task is parked (waiting on CI, time, or an event)
    status/parkedDeliberately paused with a recorded resume condition
    and removed
    queue/claimedA queue worker has claimed the task for this item
    on Nov 5, 2026
  6. dezren39 commented on Nov 5, 2026

    @dezren39
    Author

    Task 9797 is parked for 20 minutes because PR #1721 has no GitHub checks reported yet. The change is pushed at 0a302ce; focused tests, TUI typecheck, lint (0 errors), and pre-push full typecheck are locally green. Resume by checking PR checks fresh, then merge only when green and the diff still matches #1683.

  7. dezren39 commented on Nov 5, 2026

    @dezren39
    Author

    🤖 Agent note · bound

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    app/tuiPackage: tui (pkgs/ or apps/ in this repo)area/typescriptTypeScript code areaeffort/sSmall: expected under 2 hours of workkind/bugSomething isn't working (prefixed form of the classic bug label)kind/testTest coverage, test infrastructure, or test-only fixespriority/normalOrdinary priority (the default)queue/parkedQueue task is parked (waiting on CI, time, or an event)source/creatorFiled by the new-issue-creator jobstatus/parkedDeliberately paused with a recorded resume conditiontask/small-fixQueue packet kind: bounded fix with reproduction and targeted testswork/assignedAn agent holds this item via the queue (claimed task)

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions