Skip to content

[UI] Subagent status line flickers and causes layout jumping #36

@randomm

Description

@randomm

Problem

When background agents are running, the status line showing current activity (e.g., → Fuzu-Production-Db_query:) flashes very briefly and then disappears. This causes:

  1. Flickering - Status line appears for ~1 second then vanishes
  2. Layout jumping - Screen jumps when status line appears/disappears
  3. Worse with multiple agents - More agents = more jumping

Screenshots

Without status line (most of the time):

  • Shows only: ◉ Research-Specialist Task "Analyze PhD user recommendations"

With status line (brief flash):

  • Shows: ◉ Research-Specialist Task "Analyze PhD user recommendations"
  • Plus: → Fuzu-Production-Db_query:

Root Cause Analysis

THE BUG: Missing initial session registration causes race condition

When a task spawns a subagent:

  1. TaskTool creates child session (task.ts:169-199)

    • Returns sessionId in metadata: ctx.metadata({ metadata: { sessionId: session.id } })
    • Session is persisted to storage via Session.createNext
  2. Task component renders activity (index.tsx:1805-1807)

    const childMessages = createMemo(() =>
      props.metadata.sessionId ? (sync.data.message[props.metadata.sessionId] ?? []) : [],
    )
    • Immediately looks up sync.data.message[props.metadata.sessionId]
  3. Sync context message store is EMPTY for new session (sync.tsx:228-266)

    • sync.data.message[childSessionID] is undefined initially
    • Child session's messages arrive via "message.updated" events after a delay
    • During this delay, childMessages() returns [], activity() returns null
  4. Sync.session.sync() only runs on parent session (index.tsx:173-187)

    • The createEffect in Session component calls sync.session.sync(route.sessionID) for parent only
    • Child session is never synced, so its messages never populate the store via sync
  5. Result: Activity line flashes → vanishes → reappears

    • T0: Task component renders with sessionId but no messages → no activity line
    • T1 (~1 sec): First "message.updated" event arrives → activity line appears briefly
    • T2 (~2 sec): State update clears sync.data.message[childSessionId] temporarily
    • T3: When stable, activity line stays visible (if at all)

Key Files

File Lines Purpose
packages/opencode/src/cli/cmd/tui/routes/session/index.tsx 1790-1888 Task component renders subagent status
packages/opencode/src/cli/cmd/tui/routes/session/index.tsx 1805-1807 childMessages memo lookup
packages/opencode/src/cli/cmd/tui/routes/session/index.tsx 1810-1838 activity memo calculation
packages/opencode/src/cli/cmd/tui/context/sync.tsx 228-266 "message.updated" event handler
packages/opencode/src/tool/task.ts 163-199, 225-250 Child session creation and metadata

Suggested Fix Approaches

Option 1: Fetch child session messages on demand

// In packages/opencode/src/cli/cmd/tui/routes/session/index.tsx
function Task(props: ToolProps<typeof TaskTool>) {
  const sync = useSync()
  
  // NEW: Ensure child session data is loaded
  createEffect(async () => {
    const childSessionId = props.metadata.sessionId
    if (!childSessionId) return
    const hasMessages = !!sync.data.message[childSessionId]?.length
    if (!hasMessages) {
      await sync.session.sync(childSessionId)
    }
  })
  
  // ... rest of component ...
}

Option 2: Add activity placeholder state

const activity = createMemo(() => {
  const msgs = childMessages()
  const hasPending = props.part.state.status === "running" || props.part.state.status === "pending"
  
  if (msgs.length === 0 && hasPending && props.metadata.sessionId) {
    return "Starting..."
  }
  
  // ... existing logic ...
})

Option 3: Reserve layout space

Prevent jumping by always reserving space for the status line when a task is running.

Related Issues

Priority

Medium - UX issue affecting usability when running parallel agents

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions