feat: display background task count in status bar#421
Conversation
When Claude Code runs background tasks (run_in_background), the web UI now shows "N background task(s) running" with a pulsing blue indicator in the status bar instead of just "online". Detection: scans message content for "Command running in background" (task start) and "<task-notification>" (task completion). Count is tracked in-memory per session and resets on session end.
There was a problem hiding this comment.
Review mode: initial
Findings
- [Major] Background task detection miscounts when users mention the sentinel text literally.
extractBackgroundTaskDelta()stringifies the entire inbound message and counts<task-notification>/Command running in background with ID:anywhere in the payload, but the repo already distinguishes injected task notifications from normal user text (cli/src/api/apiSession.test.ts:81). Because this runs on every message before updating the session cache, a prompt likeHow do I use the <task-notification> tag?will decrementbackgroundTaskCountand can hide still-running tasks. Evidence:hub/src/sync/backgroundTasks.ts:14,hub/src/socket/handlers/cli/sessionHandlers.ts:114,cli/src/api/apiSession.test.ts:81.
Summary
- Review mode: initial
- 1 finding. The counter logic needs role/shape-aware parsing instead of whole-message string scanning.
Testing
- Not run (automation)
HAPI Bot
hub/src/sync/backgroundTasks.ts
Outdated
| * - Completed: <task-notification> user messages (sidechain prompts) | ||
| */ | ||
| export function extractBackgroundTaskDelta(messageContent: unknown): { started: number; completed: number } | null { | ||
| const str = stringify(messageContent) |
There was a problem hiding this comment.
[MAJOR] This counts sentinel strings anywhere in the JSON/stringified message, so normal text can change the counter too. The repo already treats How do I use the <task-notification> tag? as an external user message in cli/src/api/apiSession.test.ts:81, but this implementation would still decrement backgroundTaskCount for that prompt.
Suggested fix:
const record = unwrapRoleWrappedRecordEnvelope(messageContent)
if (!record) return null
let started = 0
let completed = 0
if (record.role === 'user' && Array.isArray(record.content)) {
for (const block of record.content) {
if (isObject(block)
&& block.type === 'tool_result'
&& typeof block.content === 'string'
&& block.content.includes('Command running in background with ID:')) {
started += 1
}
}
}
if (record.role === 'agent' && isObject(record.content) && record.content.type === 'output') {
const data = isObject(record.content.data) ? record.content.data : null
const message = data && isObject(data.message) ? data.message : null
const text = typeof message?.content === 'string' ? message.content.trimStart() : null
if (data?.type === 'user' && text?.startsWith('<task-notification>')) {
completed += 1
}
}
return started || completed ? { started, completed } : nullReplace naive string scanning with structured message parsing: - Task starts: only match tool_result blocks in agent-role output - Task completions: only match user-role messages starting with <task-notification> (system-injected), not user text mentioning the tag literally Addresses review feedback on false-positive risk.
There was a problem hiding this comment.
Review mode: follow-up after new commits
Findings
- [Major] Background-task completions never decrement the counter. The CLI wraps injected Claude records, including
<task-notification>, as outerrole: 'agent'/content.type: 'output', soextractBackgroundTaskDelta()never enters the newrecord.role === 'user'completion branch andbackgroundTaskCountonly grows until session end. Evidence:hub/src/sync/backgroundTasks.ts:17,cli/src/api/apiSession.ts:378,web/src/chat/normalize.test.ts:110.
Suggested fix:const record = unwrapRoleWrappedRecordEnvelope(messageContent) if (!record || record.role !== 'agent') return null if (!isObject(record.content) || record.content.type !== 'output') return null const data = isObject(record.content.data) ? record.content.data : null const started = countTaskStarts(record.content) const completed = data?.type === 'user' ? countTaskCompletions(data) : 0
Summary
- Review mode: follow-up after new commits
- 1 finding. The follow-up fixed the literal-text false positive, but the completion parser still does not match the agent-wrapped Claude message shape used in this repo.
Testing
- Not run (automation;
bununavailable in this runner). Missing coverage forextractBackgroundTaskDelta()on tool-result start and<task-notification>completion envelopes.
HAPI Bot
hub/src/sync/backgroundTasks.ts
Outdated
| if (!record) return null | ||
|
|
||
| const started = record.role === 'agent' ? countTaskStarts(record.content) : 0 | ||
| const completed = record.role === 'user' ? countTaskCompletions(record.content) : 0 |
There was a problem hiding this comment.
[MAJOR] This follow-up still misses every background-task completion. sendClaudeSessionMessage() wraps injected Claude records as outer role: 'agent' + content.type: 'output' (cli/src/api/apiSession.ts:378), and the existing normalization tests cover <task-notification> in that same shape (web/src/chat/normalize.test.ts:110). Because this branch only decrements when the outer role is 'user', the counter can only increase and the status bar stays stuck on "N background task(s) running" until session end.
Suggested fix:
const record = unwrapRoleWrappedRecordEnvelope(messageContent)
if (!record || record.role !== 'agent') return null
if (!isObject(record.content) || record.content.type !== 'output') return null
const data = isObject(record.content.data) ? record.content.data : null
const started = countTaskStarts(record.content)
const completed = data?.type === 'user' ? countTaskCompletions(data) : 0CLI wraps <task-notification> as { role: 'agent', content: { type: 'output',
data: { type: 'user', message: { content: '<task-notification>...' } } } }.
Check data.type === 'user' inside the agent output envelope to correctly
detect completions and decrement backgroundTaskCount.
There was a problem hiding this comment.
Findings
- [Minor] The new background-task status is hardcoded in English, so it bypasses the existing i18n path and regresses non-English sessions. The other connection-state labels already use translation keys, and the locale tables only define those translated variants today. Evidence
web/src/components/AssistantChat/StatusBar.tsx:90,web/src/lib/locales/en.ts:288,web/src/lib/locales/zh-CN.ts:290.
Suggested fix:text: t('misc.backgroundTasksRunning', { count: backgroundTaskCount, s: backgroundTaskCount > 1 ? 's' : '' })
Summary
- Review mode: follow-up after new commits
- 1 minor finding. The background-task parser now matches the agent-wrapped Claude shape, but the new status label regresses localization in the web UI.
Testing
- Not run (review automation). Missing targeted coverage for the new background-task status path and locale keys.
HAPI Bot
|
|
||
| if (backgroundTaskCount > 0) { | ||
| return { | ||
| text: `${backgroundTaskCount} background task${backgroundTaskCount > 1 ? 's' : ''} running`, |
There was a problem hiding this comment.
[MINOR] This new status text is hardcoded in English, while the rest of the connection-state labels already go through useTranslation(). In zh-CN, users will now see an English-only status as soon as a background task is running.
Suggested fix:
text: t('misc.backgroundTasksRunning', {
count: backgroundTaskCount,
s: backgroundTaskCount > 1 ? 's' : ''
})Add the matching misc.backgroundTasksRunning entry in web/src/lib/locales/en.ts and web/src/lib/locales/zh-CN.ts.
|
Re: i18n finding — intentionally kept in English to match Claude Code CLI's native status line, which displays "N background task(s) running" regardless of locale. Localizing this would actually diverge from the CLI behavior and break user expectations for those familiar with the terminal experience. |
When Claude Code runs background tasks (run_in_background), the web UI now shows "N background task(s) running" with a pulsing blue indicator in the status bar instead of just "online".
Detection: scans message content for "Command running in background" (task start) and "" (task completion). Count is tracked in-memory per session and resets on session end.