Skip to content

Feat/tool call timestamps#913

Open
krishnaclouds wants to merge 5 commits into
AltimateAI:mainfrom
krishnaclouds:main
Open

Feat/tool call timestamps#913
krishnaclouds wants to merge 5 commits into
AltimateAI:mainfrom
krishnaclouds:main

Conversation

@krishnaclouds
Copy link
Copy Markdown

@krishnaclouds krishnaclouds commented Jun 7, 2026

Summary

Adds visibility into how long tool calls take inside the TUI — both per-call and at the session level.

  • Per-tool timing. Every tool call now shows its start time and elapsed duration right next to it (HH:MM:SS · 234ms for finished, HH:MM:SS · running 12.3s for in-flight). Live-ticks at 1Hz while running, freezes on completion. Tool calls running longer than 30s flip to the theme's warning color so slow calls stand out.
  • Sidebar total. The session sidebar's Context block gains a in tools line that sums execution time across every tool call in the session, ticking live while any tool is running.
  • Toggle. Bound to the existing timestamps setting. Adds /timing and /toggle-timing slash aliases alongside the existing /timestamps. Renames the menu entry to "tool timing." Default changes from hide → show; users with an explicit prior choice keep it (kv persistence).
  • Refactor. A small routes/session/timing.ts module owns SLOW_TOOL_MS, the Elapsed type, and the format helper — replaces three copies of the timing-string builder across InlineTool/BlockTool with one call.
  • Helper. New Locale.clockTime(ms) produces 24-hour HH:MM:SS.

What changed and why?

Test Plan

  • bun test packages/opencode/test/util/locale.test.ts — passes (new clockTime cases)
  • bun turbo typecheck — passes (pre-push hook, all 5 packages green)
  • Run a fast tool in a session → inline row renders HH:MM:SS · and freezes on completion
  • Run a slow tool (>30s) → inline timing turns warning-colored while running, locks to muted on completion
  • Sidebar Context block shows in tools and gains (running) while a tool is in-flight, ticking once per second
  • /timing, /toggle-timing, and /timestamps all toggle visibility; preference persists across sessions
  • Block-style tools (spinner variants) render timing inside the spinner label

Checklist

  • Tests added/updated — unit coverage for Locale.clockTime; reactive hooks (useElapsed, toolTime) intentionally not unit-tested (no lightweight harness for this Solid-on-TUI renderer; tracked as known testing debt)
  • Documentation updated (if needed) — no docs reference the prior /timestamps UX; new aliases discoverable via the existing command palette
  • CHANGELOG updated (if user-facing) — toggle default flip from hide → show is the only behavior change for existing users; worth a one-liner if this repo keeps a CHANGELOG

Summary by cubic

Adds tool-call timing to the TUI: each call shows start time and elapsed duration, and the sidebar shows total time spent in tools. Timing is on by default and can be toggled with new slash commands.

  • New Features

    • Per-call timing: displays HH:MM:SS · <duration>; live-ticks while running, freezes on completion.
    • Slow-call highlight: running tools ≥30s switch to the warning color.
    • Works in both inline and spinner tool UIs.
    • Session sidebar shows total time spent in tools and adds “(running)” when active.
    • Toggle via the existing timestamps setting; new aliases /timing and /toggle-timing.
  • Migration

    • Timing visibility default changes from hide to show. Existing explicit user preferences persist.

Written for commit cb587c4. Summary will update on new commits.

Review in cubic

Summary by CodeRabbit

  • New Features
    • Tools now display individual execution times with real-time updates for running processes
    • Session sidebar shows cumulative time spent across all tools in the current session
    • Tool timestamps feature now enabled by default
    • Tools running longer than 30 seconds are highlighted with a warning indicator

Each tool block (BlockTool + InlineTool) now renders `HH:MM:SS · 1.2s`
in the title row. Running tools tick the elapsed counter at 1Hz and
freeze on completion; tools running >=30s switch to theme.warning so
slow calls stand out at a glance.

- Add Locale.clockTime() for 24-hour HH:MM:SS formatting.
- Add useElapsed(part) hook: returns {start, ms, running}; live-ticks
  only while status === "running" and cleans up the interval on
  transition to completed/error.
- Gate visibility behind the existing showTimestamps toggle; default
  flipped from "hide" -> "show". Toggle relabeled "Show tool timing"
  and aliased to /timing in addition to /timestamps.
- Pending parts (no time.start yet) render no timing row, so the UX
  stays clean during the brief pre-running window.

Tests: 2 new Locale.clockTime cases; existing TUI suite (157) still
passes; tsgo typecheck clean.
Aggregates duration across every ToolPart in the session — completed
and error tools by (end - start), the currently running tool by
(now - start), live-ticking once a second only while a tool is running.
Renders under the existing Context block as '… in tools' (with a
'(running)' suffix when active), hidden when zero.
Pulls SLOW_TOOL_MS, the Elapsed type, and the 'HH:MM:SS · running …'
suffix builder into routes/session/timing.ts. Collapses three copies
of the timing format string across InlineTool and BlockTool down to
a single formatElapsed() call, and drops a redundant 'as ToolPart'
cast in the sidebar — the part.type === 'tool' discriminant already
narrows the union.
Copy link
Copy Markdown

@claude claude Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Claude Code Review

This pull request is from a fork — automated review is disabled. A repository maintainer can comment @claude review to run a one-time review.

@github-actions
Copy link
Copy Markdown

github-actions Bot commented Jun 7, 2026

Hey! Your PR title Feat/tool call timestamps doesn't follow conventional commit format.

Please update it to start with one of:

  • feat: or feat(scope): new feature
  • fix: or fix(scope): bug fix
  • docs: or docs(scope): documentation changes
  • chore: or chore(scope): maintenance tasks
  • refactor: or refactor(scope): code refactoring
  • test: or test(scope): adding or updating tests

Where scope is the package name (e.g., app, desktop, opencode).

See CONTRIBUTING.md for details.

@github-actions
Copy link
Copy Markdown

github-actions Bot commented Jun 7, 2026

This PR doesn't fully meet our contributing guidelines and PR template.

What needs to be fixed:

  • PR description is missing required template sections. Please use the PR template.

Please edit this PR description to address the above within 2 hours, or it will be automatically closed.

If you believe this was flagged incorrectly, please let a maintainer know.

@github-actions
Copy link
Copy Markdown

github-actions Bot commented Jun 7, 2026

👋 This PR was automatically closed by our quality checks.

Common reasons:

  • New GitHub account with limited contribution history
  • PR description doesn't meet our guidelines
  • Contribution appears to be AI-generated without meaningful review

If you believe this was a mistake, please open an issue explaining your intended contribution and a maintainer will help you.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Jun 7, 2026

Review Change Stack

📝 Walkthrough

Walkthrough

The PR adds elapsed-time display for tool execution in the Session UI. A new timing module defines formatting utilities and slow-tool thresholds. Per-tool timing is computed via a 1Hz polling hook (useElapsed) that freezes duration when tools complete, with conditional rendering in InlineTool and BlockTool. The sidebar aggregates session-wide tool time across all tools and updates every second while any tool runs.

Changes

Tool Elapsed-Time Display in Session UI

Layer / File(s) Summary
Timing utilities and locale helpers
packages/opencode/src/cli/cmd/tui/routes/session/timing.ts, packages/opencode/src/util/locale.ts, packages/opencode/test/util/locale.test.ts
New timing.ts module exports SLOW_TOOL_MS (30s threshold), Elapsed interface, and formatElapsed formatter that combines clock time and duration. Locale.clockTime formats timestamps as 24-hour HH:MM:SS with zero-padded components, verified by test suite.
Per-tool elapsed-time display in Session UI
packages/opencode/src/cli/cmd/tui/routes/session/index.tsx
useElapsed hook polls tool running state every 1s via setInterval and returns elapsed duration object for formatElapsed; returns undefined during pending state and freezes end-start when completed/error. InlineTool and BlockTool render elapsed time conditionally based on ctx.showTimestamps(), using SLOW_TOOL_MS to apply warning color for slow tools. Session timestamps visibility now defaults to "show" instead of "hide", and command palette entry expanded with timing-related aliases.
Session-wide tool execution time in Sidebar
packages/opencode/src/cli/cmd/tui/routes/session/sidebar.tsx
Sidebar computes total session tool time by flattening all ToolPart entries, detecting running state, and updating a 1Hz signal while tools execute. toolTime memo sums elapsed milliseconds across running (start-to-now) and completed/error (start-to-end) tool parts, with UI displaying "(running)" indicator while any tool is active.

Sequence Diagram(s)

sequenceDiagram
  participant ToolPart
  participant useElapsed
  participant InlineTool
  participant Renderer
  ToolPart->>useElapsed: check running state every 1s
  useElapsed->>useElapsed: compute elapsed: running ? now-start : end-start
  useElapsed->>InlineTool: return Elapsed object
  InlineTool->>InlineTool: compute timingColor from SLOW_TOOL_MS threshold
  InlineTool->>Renderer: render formatElapsed(elapsed) when timestamps enabled
Loading
sequenceDiagram
  participant SessionMessages
  participant ToolParts
  participant Sidebar
  participant Display
  SessionMessages->>ToolParts: flatten all tool parts
  ToolParts->>Sidebar: detect running state
  Sidebar->>Sidebar: setInterval: update nowToolTime every 1s
  Sidebar->>Sidebar: sum toolTime: running ? (now-start) + completed
  Sidebar->>Display: show duration line with "(running)" flag
Loading

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

Poem

🐰 Behold the rabbits of time, now visible and fleet—
Each tool's journey traced in bright digital heat,
From first hop to last, the seconds are shown,
In clocks and durations, forever known!
Slow tools wear their warning like proud colored stains. ⏰✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 30.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Feat/tool call timestamps' directly describes the main feature added: tool call timing visibility in the TUI.
Description check ✅ Passed The description includes all required sections (Summary, Test Plan, Checklist) and thoroughly documents changes, testing, and migration impacts.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions
Copy link
Copy Markdown

github-actions Bot commented Jun 7, 2026

👋 This PR was automatically closed by our quality checks.

Common reasons:

  • New GitHub account with limited contribution history
  • PR description doesn't meet our guidelines
  • Contribution appears to be AI-generated without meaningful review

If you believe this was a mistake, please open an issue explaining your intended contribution and a maintainer will help you.

1 similar comment
@github-actions
Copy link
Copy Markdown

github-actions Bot commented Jun 7, 2026

👋 This PR was automatically closed by our quality checks.

Common reasons:

  • New GitHub account with limited contribution history
  • PR description doesn't meet our guidelines
  • Contribution appears to be AI-generated without meaningful review

If you believe this was a mistake, please open an issue explaining your intended contribution and a maintainer will help you.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/opencode/src/cli/cmd/tui/routes/session/index.tsx`:
- Around line 1698-1710: The elapsed can be negative on the pending→running
transition because now() may be older than s.time.start; fix by ensuring
computed ms is never negative: in the createMemo for Elapsed (the block using
part(), s.time.start and now()), clamp ms to Math.max(0, now() - s.time.start)
for the running case (or alternatively update the now signal when a part's
status changes to "running" inside the createEffect to setNow(Date.now()));
modify the logic in the createMemo/createEffect that references now, part(), and
s.time.start so the first rendered duration is non-negative.

In `@packages/opencode/src/cli/cmd/tui/routes/session/sidebar.tsx`:
- Around line 80-93: The running-time ticker can be stale until the first
interval tick; inside the createEffect that watches hasRunningTool(),
immediately seed nowToolTime() (call setNowToolTime(Date.now())) when a running
tool is detected before creating the setInterval, then create the interval as
before and keep the onCleanup(clearInterval) logic; update the createEffect
block around hasRunningTool(), nowToolTime, setNowToolTime, and the interval id
to ensure the first tick uses a fresh timestamp.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: d935906f-6876-4b20-be3a-ed354fd2d574

📥 Commits

Reviewing files that changed from the base of the PR and between 5ffeadb and cb587c4.

📒 Files selected for processing (5)
  • packages/opencode/src/cli/cmd/tui/routes/session/index.tsx
  • packages/opencode/src/cli/cmd/tui/routes/session/sidebar.tsx
  • packages/opencode/src/cli/cmd/tui/routes/session/timing.ts
  • packages/opencode/src/util/locale.ts
  • packages/opencode/test/util/locale.test.ts

Comment on lines +1698 to +1710
const [now, setNow] = createSignal(Date.now())
createEffect(() => {
if (part()?.state.status !== "running") return
const id = setInterval(() => setNow(Date.now()), 1000)
onCleanup(() => clearInterval(id))
})
return createMemo<Elapsed | undefined>(() => {
const p = part()
if (!p) return undefined
const s = p.state
if (s.status === "pending") return undefined
if (s.status === "running") return { start: s.time.start, ms: now() - s.time.start, running: true }
return { start: s.time.start, ms: s.time.end - s.time.start, running: false }
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Prevent negative elapsed values on pending→running transition.

now may be older than s.time.start right after a tool starts, so the first rendered duration can go negative until the first 1s tick.

Suggested fix
 function useElapsed(part: () => ToolPart | undefined) {
   const [now, setNow] = createSignal(Date.now())
   createEffect(() => {
     if (part()?.state.status !== "running") return
+    setNow(Date.now())
     const id = setInterval(() => setNow(Date.now()), 1000)
     onCleanup(() => clearInterval(id))
   })
   return createMemo<Elapsed | undefined>(() => {
     const p = part()
     if (!p) return undefined
     const s = p.state
     if (s.status === "pending") return undefined
-    if (s.status === "running") return { start: s.time.start, ms: now() - s.time.start, running: true }
-    return { start: s.time.start, ms: s.time.end - s.time.start, running: false }
+    if (s.status === "running") return { start: s.time.start, ms: Math.max(0, now() - s.time.start), running: true }
+    return { start: s.time.start, ms: Math.max(0, s.time.end - s.time.start), running: false }
   })
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/opencode/src/cli/cmd/tui/routes/session/index.tsx` around lines 1698
- 1710, The elapsed can be negative on the pending→running transition because
now() may be older than s.time.start; fix by ensuring computed ms is never
negative: in the createMemo for Elapsed (the block using part(), s.time.start
and now()), clamp ms to Math.max(0, now() - s.time.start) for the running case
(or alternatively update the now signal when a part's status changes to
"running" inside the createEffect to setNow(Date.now())); modify the logic in
the createMemo/createEffect that references now, part(), and s.time.start so the
first rendered duration is non-negative.

Comment on lines +80 to +93
const [nowToolTime, setNowToolTime] = createSignal(Date.now())
createEffect(() => {
if (!hasRunningTool()) return
const id = setInterval(() => setNowToolTime(Date.now()), 1000)
onCleanup(() => clearInterval(id))
})
const toolTime = createMemo(() => {
let ms = 0
const tick = nowToolTime()
for (const part of toolParts()) {
const s = part.state
if (s.status === "running") ms += tick - s.time.start
else if (s.status === "completed" || s.status === "error") ms += s.time.end - s.time.start
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Seed the running-time ticker immediately to avoid stale/negative totals.

When a tool starts after sidebar mount, nowToolTime() can be stale until the first interval tick, which can yield negative elapsed math (tick - start) transiently.

Suggested fix
   const [nowToolTime, setNowToolTime] = createSignal(Date.now())
   createEffect(() => {
     if (!hasRunningTool()) return
+    setNowToolTime(Date.now())
     const id = setInterval(() => setNowToolTime(Date.now()), 1000)
     onCleanup(() => clearInterval(id))
   })
   const toolTime = createMemo(() => {
     let ms = 0
     const tick = nowToolTime()
     for (const part of toolParts()) {
       const s = part.state
-      if (s.status === "running") ms += tick - s.time.start
-      else if (s.status === "completed" || s.status === "error") ms += s.time.end - s.time.start
+      if (s.status === "running") ms += Math.max(0, tick - s.time.start)
+      else if (s.status === "completed" || s.status === "error") ms += Math.max(0, s.time.end - s.time.start)
     }
     return ms
   })
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/opencode/src/cli/cmd/tui/routes/session/sidebar.tsx` around lines 80
- 93, The running-time ticker can be stale until the first interval tick; inside
the createEffect that watches hasRunningTool(), immediately seed nowToolTime()
(call setNowToolTime(Date.now())) when a running tool is detected before
creating the setInterval, then create the interval as before and keep the
onCleanup(clearInterval) logic; update the createEffect block around
hasRunningTool(), nowToolTime, setNowToolTime, and the interval id to ensure the
first tick uses a fresh timestamp.

Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

4 issues found across 5 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="packages/opencode/src/cli/cmd/tui/routes/session/index.tsx">

<violation number="1" location="packages/opencode/src/cli/cmd/tui/routes/session/index.tsx:1709">
P2: Clamp elapsed to zero to prevent negative duration on first render. The `now` signal is seeded at mount time but `s.time.start` may be newer if the tool starts later, yielding a negative `ms` until the next interval tick. Use `Math.max(0, now() - s.time.start)`.</violation>

<violation number="2" location="packages/opencode/src/cli/cmd/tui/routes/session/index.tsx:1753">
P2: InlineTool spinner branch omits elapsed timing display</violation>
</file>

<file name="packages/opencode/src/util/locale.ts">

<violation number="1" location="packages/opencode/src/util/locale.ts:13">
P1: `clockTime` may render midnight as `24:00:00` because `hour12: false` alone does not guarantee a 0-23 hour cycle in Node.js ICU. Add `hourCycle: "h23"` to enforce standard 24-hour format.</violation>
</file>

<file name="packages/opencode/src/cli/cmd/tui/routes/session/sidebar.tsx">

<violation number="1" location="packages/opencode/src/cli/cmd/tui/routes/session/sidebar.tsx:91">
P2: Clamp to zero to prevent transient negative tool-time contribution. `nowToolTime()` is seeded at sidebar mount; if a tool starts later, `tick - s.time.start` can be negative until the interval fires. Use `Math.max(0, tick - s.time.start)`.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic


export function clockTime(input: number): string {
const date = new Date(input)
return date.toLocaleTimeString(undefined, { hour12: false, hour: "2-digit", minute: "2-digit", second: "2-digit" })
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: clockTime may render midnight as 24:00:00 because hour12: false alone does not guarantee a 0-23 hour cycle in Node.js ICU. Add hourCycle: "h23" to enforce standard 24-hour format.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/opencode/src/util/locale.ts, line 13:

<comment>`clockTime` may render midnight as `24:00:00` because `hour12: false` alone does not guarantee a 0-23 hour cycle in Node.js ICU. Add `hourCycle: "h23"` to enforce standard 24-hour format.</comment>

<file context>
@@ -8,6 +8,11 @@ export namespace Locale {
 
+  export function clockTime(input: number): string {
+    const date = new Date(input)
+    return date.toLocaleTimeString(undefined, { hour12: false, hour: "2-digit", minute: "2-digit", second: "2-digit" })
+  }
+
</file context>

error()?.includes("user dismissed"),
)

const elapsed = useElapsed(() => props.part)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: InlineTool spinner branch omits elapsed timing display

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/opencode/src/cli/cmd/tui/routes/session/index.tsx, line 1753:

<comment>InlineTool spinner branch omits elapsed timing display</comment>

<file context>
@@ -1729,6 +1750,14 @@ function InlineTool(props: {
       error()?.includes("user dismissed"),
   )
 
+  const elapsed = useElapsed(() => props.part)
+  const timingVisible = createMemo(() => ctx.showTimestamps() && !!elapsed())
+  const timingColor = createMemo(() => {
</file context>

if (!p) return undefined
const s = p.state
if (s.status === "pending") return undefined
if (s.status === "running") return { start: s.time.start, ms: now() - s.time.start, running: true }
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Clamp elapsed to zero to prevent negative duration on first render. The now signal is seeded at mount time but s.time.start may be newer if the tool starts later, yielding a negative ms until the next interval tick. Use Math.max(0, now() - s.time.start).

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/opencode/src/cli/cmd/tui/routes/session/index.tsx, line 1709:

<comment>Clamp elapsed to zero to prevent negative duration on first render. The `now` signal is seeded at mount time but `s.time.start` may be newer if the tool starts later, yielding a negative `ms` until the next interval tick. Use `Math.max(0, now() - s.time.start)`.</comment>

<file context>
@@ -1690,6 +1692,25 @@ function ToolTitle(props: { fallback: string; when: any; icon: string; children:
+    if (!p) return undefined
+    const s = p.state
+    if (s.status === "pending") return undefined
+    if (s.status === "running") return { start: s.time.start, ms: now() - s.time.start, running: true }
+    return { start: s.time.start, ms: s.time.end - s.time.start, running: false }
+  })
</file context>
Suggested change
if (s.status === "running") return { start: s.time.start, ms: now() - s.time.start, running: true }
if (s.status === "running") return { start: s.time.start, ms: Math.max(0, now() - s.time.start), running: true }

const tick = nowToolTime()
for (const part of toolParts()) {
const s = part.state
if (s.status === "running") ms += tick - s.time.start
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Clamp to zero to prevent transient negative tool-time contribution. nowToolTime() is seeded at sidebar mount; if a tool starts later, tick - s.time.start can be negative until the interval fires. Use Math.max(0, tick - s.time.start).

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/opencode/src/cli/cmd/tui/routes/session/sidebar.tsx, line 91:

<comment>Clamp to zero to prevent transient negative tool-time contribution. `nowToolTime()` is seeded at sidebar mount; if a tool starts later, `tick - s.time.start` can be negative until the interval fires. Use `Math.max(0, tick - s.time.start)`.</comment>

<file context>
@@ -62,6 +62,39 @@ export function Sidebar(props: { sessionID: string; overlay?: boolean }) {
+    const tick = nowToolTime()
+    for (const part of toolParts()) {
+      const s = part.state
+      if (s.status === "running") ms += tick - s.time.start
+      else if (s.status === "completed" || s.status === "error") ms += s.time.end - s.time.start
+    }
</file context>
Suggested change
if (s.status === "running") ms += tick - s.time.start
if (s.status === "running") ms += Math.max(0, tick - s.time.start)

Copy link
Copy Markdown

@dev-punia-altimate dev-punia-altimate left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Multi-Persona Review — Verdict: block

The PR introduces a critical bug in session-level tool timing calculation that could produce incorrect cumulative durations when multiple tools run simultaneously, and changes a user-facing default (timestamps from hide to show) without documented product approval, violating project compliance standards.

14/14 agents completed · 249s · 3 findings (1 critical, 1 high, 0 medium)

Critical

  • [code-reviewer] The toolTime calculation uses Date.now() as a live tick for running tools, but does not account for the possibility that multiple tools may be running simultaneously and their start times may be from different epochs. This could lead to incorrect cumulative timing if tools start at different times and the live tick advances uniformly. → packages/opencode/src/cli/cmd/tui/routes/session/sidebar.tsx:140
    • 💡 Instead of using a single nowToolTime signal for all tools, compute elapsed time per tool individually using their own start time and the current time, avoiding shared tick logic that assumes synchronized timing.

High

  • [code-reviewer] The default value for 'timestamps' was changed from 'hide' to 'show'. While the PR claims this is intentional and user preferences persist, the CLAUDE.md in the project root (if present) may explicitly require backward-compatible defaults for UI settings unless explicitly approved by product. This change alters user experience without a documented product decision. → packages/opencode/src/cli/cmd/tui/routes/session/index.tsx:154
    • 💡 Verify that a product decision or CLAUDE.md rule explicitly permits flipping the default from hide to show. If not, revert to 'hide' and document the change in a follow-up PR with product sign-off.

Low

  • [code-reviewer] The useElapsed hook calls clearInterval(id) on cleanup, but does not check if id is defined before clearing. If the effect runs and immediately exits (e.g., if part() is undefined), id may be undefined, leading to a potential runtime warning or error in some environments. → packages/opencode/src/cli/cmd/tui/routes/session/index.tsx:1750
    • 💡 Wrap clearInterval(id) in a conditional: if (id) clearInterval(id).

Multi-Persona Review · vllm:qwen3-next-80b (waves) + vllm-fallback (synth) ·

@@ -107,6 +140,13 @@ export function Sidebar(props: { sessionID: string; overlay?: boolean }) {
<text fg={theme.textMuted}>{context()?.tokens ?? 0} tokens</text>
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[CRITICAL · code-reviewer] The toolTime calculation uses Date.now() as a live tick for running tools, but does not account for the possibility that multiple tools may be running simultaneously and their start times may be from different epochs. This could lead to incorrect cumulative timing if tools start at different times and the live tick advances uniformly.

💡 Suggestion: Instead of using a single nowToolTime signal for all tools, compute elapsed time per tool individually using their own start time and the current time, avoiding shared tick logic that assumes synchronized timing.

Confidence: 90/100

@@ -152,7 +154,7 @@ export function Session() {
const [sidebarOpen, setSidebarOpen] = createSignal(false)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[HIGH · code-reviewer] The default value for 'timestamps' was changed from 'hide' to 'show'. While the PR claims this is intentional and user preferences persist, the CLAUDE.md in the project root (if present) may explicitly require backward-compatible defaults for UI settings unless explicitly approved by product. This change alters user experience without a documented product decision.

💡 Suggestion: Verify that a product decision or CLAUDE.md rule explicitly permits flipping the default from hide to show. If not, revert to 'hide' and document the change in a follow-up PR with product sign-off.

Confidence: 70/100

@@ -1729,6 +1750,14 @@ function InlineTool(props: {
error()?.includes("user dismissed"),
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[LOW · code-reviewer] The useElapsed hook calls clearInterval(id) on cleanup, but does not check if id is defined before clearing. If the effect runs and immediately exits (e.g., if part() is undefined), id may be undefined, leading to a potential runtime warning or error in some environments.

💡 Suggestion: Wrap clearInterval(id) in a conditional: if (id) clearInterval(id).

Confidence: 85/100

@anandgupta42
Copy link
Copy Markdown
Contributor

@krishnaclouds Thanks for the contribution. Can you please address the code review comments?

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants