Skip to content

Freebuff Desktop — phantom empty "New thread" tabs auto-created (home-tab flag never persists) #1023

Description

@humvnpvrvdox777

Freebuff Desktop — phantom empty "New thread" tabs are auto-created (home-tab flag never persists)

Product: Freebuff Desktop (Electron app)
Version: 0.0.62 (Windows x64)
Severity: medium (UX/data hygiene — pollutes the workspace DB with empty orphan threads; no data loss)
Reported: 2026-08-15


Summary

Freebuff Desktop silently creates empty "New thread" tabs on its own, repeatedly, throughout a session.
In one day the app created 7+ empty threads without any user action (17:11, 17:42, 17:44, 17:47, 18:51,
19:18:53, 19:19:41 local time). The threads are created with harness_id = NULL, zero messages, and stay in
the workspace DB (desktop-v2.db) as orphan rows. Users see tabs "opening by themselves".

Reproduction

No special steps needed — it happens on its own in any long session:

  1. Start Freebuff Desktop and open a workspace.
  2. Use the app normally (send prompts in any thread, switch tabs, let turns run).
  3. Periodically, an empty "New thread" tab appears without any user action — and a new thread row is
    inserted into desktop-v2.db (threads table, harness_id = NULL, no messages).

Observed trigger in one session: sending a prompt (which got queued because the previous turn was still
running) was followed ~26 s later by a phantom thread; a second one appeared 48 s later; closing the home
tab re-created a new one immediately (verified live: close at 19:35:25, new thread created at 19:35:23).

Root cause (verified in the shipped bundle)

Three behaviors combine:

1. The home tab flag never survives persistence

resources/orchestrator/orchestrator.js:

  • zTabs = list(object({ id, projectPath, openerId, file })) — the schema has no home field, so
    normalizeWorkspace() strips home on every read (zod drops unknown keys).
  • writeWorkspace(state) { this.write({ workspace: normalizeWorkspace(state) }) }normalizeWorkspace
    is applied before writing, so even though coerceTabs preserves home:true on the write path, the
    value is removed again before it reaches state.json.
  • Result: ~/.config/freebuff-desktop/state.json never contains a home flag. Confirmed live — the file
    currently holds 4 tabs, none with home.

2. The UI auto-creates a home tab whenever none exists

resources/orchestrator/ui/assets/index-BRA_upWN.js (minified, function hJ):

function hJ(e){const t=L.useRef(!1);L.useEffect(()=>{
  e||t.current||(t.current=!0,wy({pickProject:!1,home:!0}).finally(()=>{t.current=!1}))
},[e])}

hJ(t) is mounted in the main window component (uJ) with t = tabs.some(p => p.home). Whenever t is
falsy (at mount, or whenever the flag flips), it calls wy({pickProject:false, home:true}) → creates a
new empty thread and opens it as the "Home" tab. Because of bug #1, after any UI (re)initialization the
app always believes there is no home tab.

3. Sending a message from the home tab un-homes it, triggering another auto-create

In the composer (j9, submit handler Re):

o ? (edit) : Le ? U(e,ee,r,s) : (n && Z(e), O(e,ee,r,s))   // Z = promoteHome

n is the home prop of the composer. When the user sends a normal message from the home tab,
promoteHome(e) runs first — it removes the home flag from that tab:

promoteHome(n){const i=t().tabs.find(r=>r.id===n);
  i!=null&&i.home&&(e(r=>({tabs:r.tabs.map(s=>s.id===n?{...s,home:void 0}:s)})),mi())}

tabs.some(p=>p.home) becomes false → hJ fires → a new empty home tab is created while the user
keeps working in the old thread. The old home tab is not closed in the UI, and its thread row stays in
the DB forever (status open) unless manually closed.

Also note openTab(home:true) replaces the previous home tab in the UI store (tabs: [{new home}, ...rest])
without closing the old thread — so each replacement leaves another orphan row.

Impact

  • Multiple empty, unnamed threads accumulate in desktop-v2.db per session (7+ in one day here).
  • Users see unexplained tabs appearing; the "Home" tab silently rotates.
  • Any automation that watches the DB for new threads gets spurious entries.

Suggested fix

  1. Persist home: add home: zBoolean.optional().catch(undefined) (or equivalent) to the zTabs
    schema in orchestrator.js, and stop re-normalizing on write (writeWorkspace should write what the
    UI sent, or normalizeWorkspace must not drop the flag).
  2. Don't un-home on send: promoteHome in the composer submit handler looks unintended — sending a
    message from the home tab should not strip the home flag (and should certainly not spawn a replacement
    tab). If the intent is "once used, the home tab becomes a normal tab", create the replacement only
    once per session at boot
    , not on every message, and close the old one explicitly.
  3. Make hJ non-recursive: gate the auto-create to initial mount only (e.g., guard with a module-level
    "created once" flag), so closing/replacing a home tab never re-spawns one.

Environment

  • Windows 11 x64, Freebuff Desktop 0.0.62 (install dir %LOCALAPPDATA%\Programs\@codebufffreebuff-desktop)
  • Workspace state: ~/.config/freebuff-desktop/state.json
  • Thread DB: <workspace>/.freebuff/desktop-v2.db

Metadata

Metadata

Assignees

No one assigned

    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