Skip to content

Desktop: custom project icons save but never render in projects list (v1.15.12) #29807

Closed as not planned

Description

@george-mcintyre

Description

In the opencode desktop app, custom project icons are saved correctly but never rendered in the projects list — each project shows the default colored letter instead of the chosen image.

Repro: Edit a project → choose an image file as its icon → Save. The selection persists, but the projects list keeps showing the default letter.

Root cause (investigated)

The icon is persisted. In the desktop data store the project record contains:

"icon": { "color": "pink", "override": "data:image/png;base64,iVBORw0K…" }

i.e. the chosen image is stored as a base64 data-URI in icon.override, alongside a fallback icon.color. The projects-list UI appears to render the color fallback and never uses icon.override.

I verified this across 5 projects in ~/Library/Application Support/ai.opencode.desktop/opencode.workspace.*.dat:

icon size type fallback color renders?
226 KB PNG pink no
40 KB JPEG lime no
30 KB PNG purple no
19 KB PNG mint no
3 KB JPEG pink no

Every stored override is a valid, fully-decodable image (correct PNG/JPEG magic bytes). So:

  • Not a save bug — the data is correct and complete.
  • Not size-dependent — a 3 KB icon fails identically to a 226 KB one.
  • Not format-dependent — PNG and JPEG both fail.
  • No errors logged — main.log / renderer.log show only benign ResizeObserver loop… warnings and update checks; nothing about icon/image decode or render.

Conclusion: the projects-list icon component isn't consuming icon.override; it falls back to the colored letter.

OpenCode version

Desktop app v1.15.12 (mac arm64), channel latest (confirmed up to date; no newer version available).

Steps to reproduce

  1. Open the opencode desktop app.
  2. Edit a project and set its icon to an image file (PNG or JPEG), then Save.
  3. Observe the projects list — it shows the default colored letter, not the chosen image.
  4. (Optional) Inspect ~/Library/Application Support/ai.opencode.desktop/opencode.workspace.*.dat: the project record's icon.override contains the correct base64 data-URI.

Operating System

macOS 26.5 (arm64)

Notes

Data store path: ~/Library/Application Support/ai.opencode.desktop/. Happy to provide a sanitized sample record or logs if useful.

Activity

  1. github-actions commented on May 29, 2026

    @github-actions
    Contributor

    This issue might be a duplicate of existing issues. Please check:

    Please check these issues and consider commenting there or linking them if your report covers the same underlying bug.

  2. george-mcintyre commented on Jul 11, 2026

    @george-mcintyre
    Author

    Update — root cause found (reverse-engineered the v1.17.18 desktop bundle)

    My original "the UI ignores icon.override" theory was close but wrong in the
    details. The real problem is that the desktop app reads the project icon from a
    different store key than the one the icon-setting path writes to
    . Confirmed by
    disassembling OpenCode.app/Contents/Resources/app.asar (v1.17.18) and by
    inspecting my own ~/Library/Application Support/ai.opencode.desktop/opencode.workspace.*.dat.

    There are two separate workspace stores

    The app persists two independent per-workspace stores (see Persist.serverWorkspace):

    Persist.serverWorkspace(scope, directory, "project", ["project.v1"]) // key: workspace:project
    Persist.serverWorkspace(scope, directory, "icon",    ["icon.v1"])    // key: workspace:icon
    • workspace:project value shape: {"value":{"icon":{"color":...,"override":"data:..."},"commands":{},"name":""}}
    • workspace:icon value shape: {"value":"data:image/png;base64,..."}

    The renderer reads from workspace:icon, not workspace:project

    The child store that feeds the projects-list icon is built as:

    const initialMeta = meta[0].value;   // workspace:project store
    const initialIcon = icon2[0].value;  // workspace:icon store
    ...
    const child2 = createStore({
      project: "",
      projectMeta: initialMeta,
      icon: initialIcon,                 // <-- this is what renders
      ...
    });

    So the rendered icon comes only from the workspace:icon store. The
    icon.override inside workspace:project is never read for rendering.

    The supported write path is projectIcon(directory, value), which correctly
    writes the icon store:

    function projectIcon(directory, value) {
      const key2 = pathKey(directory);
      const [store2, setStore] = ensureChild(directory);
      const cached = iconCache.get(key2);
      if (!cached) return;
      if (store2.icon === value) return;
      cached.setStore("value", value);   // -> workspace:icon = {"value": <data-uri>}
      setStore("icon", value);
    }

    So the bug is one of:

    1. The Edit → set icon UI action wrote the data-URI into
      workspace:project → value.icon.override (legacy location) instead of
      calling projectIcon() / writing workspace:icon; or
    2. There was a schema migration to the standalone icon store and existing
      icons in workspace:project.icon.override were never migrated forward.

    Either way, projects whose icon lives only in workspace:project.icon.override
    render the colored-letter fallback, while a project with workspace:icon set
    renders correctly. I confirmed this empirically: the one project on my machine
    that had workspace:icon populated rendered its custom image; the five that only
    had workspace:project.icon.override did not.

    Confirmed workaround

    Writing workspace:icon = {"value":"data:image/<type>;base64,<...>"} directly into
    each project's opencode.workspace.<head>.<checksum>.dat (app fully quit, then
    relaunched) makes all custom icons render immediately. The .dat filename is
    derived by the app as:

    function workspaceStorage(dir) {
      const head = (dir.slice(0, 12) || "workspace").replace(/[^a-zA-Z0-9._-]/g, "-");
      const sum  = checksum(dir) ?? "0"; // FNV-1a 32-bit -> base36
      return `opencode.workspace.${head}.${sum}.dat`;
    }

    Suggested fix

    • Make the set-icon UI action call projectIcon(directory, dataUri) (i.e.
      write the icon store), not workspace:project.icon.override; and/or
    • Add a one-time migration: on load, if workspace:icon.value is empty but
      workspace:project.value.icon.override is set, copy the override into the
      icon store (projectIcon(dir, override)) so existing users' icons appear
      without manual intervention.

    Environment: desktop app v1.17.18 (mac arm64), macOS 26.5. Original report was
    v1.15.12; the two-store split is still present in 1.17.18.

  3. github-actions commented on Sep 10, 2026

    @github-actions
    Contributor

    To stay organized issues are automatically closed after 60 days of no activity. If the issue is still relevant please open a new one.

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

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions