Skip to content

feat: real-time task run logs on /tasks/[runId]#1531

Merged
sweetmantech merged 7 commits intotestfrom
sweetmantech/myc-4168-chat-tasksrunid-observability-show-task-logs
Feb 16, 2026
Merged

feat: real-time task run logs on /tasks/[runId]#1531
sweetmantech merged 7 commits intotestfrom
sweetmantech/myc-4168-chat-tasksrunid-observability-show-task-logs

Conversation

@sweetmantech
Copy link
Copy Markdown
Collaborator

@sweetmantech sweetmantech commented Feb 16, 2026

Summary

  • Add getTaskRunStatus fetch function for GET /api/tasks/runs with typed TaskRunStatus response
  • Add useTaskRunStatus polling hook (3s interval, stops on terminal state) following useReportData pattern
  • Add RunLogsList component with auto-scroll for log entries
  • Replace placeholder RunPage with full observability UI: status indicator, current step, logs, error display, duration

Test plan

  • pnpm build passes type checks
  • Create sandbox → verify redirect to /tasks/[runId]
  • Watch status/logs update in real-time (3s polling)
  • Verify polling stops on completion or failure

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Added run status display with visual indicators (icon, label, color)
    • Added auto-scrolling logs viewer with "Waiting for logs..." placeholder
    • Added copy-to-clipboard functionality for run ID
    • Added task duration and current step display
    • Added detailed error messaging for failed runs
    • Enabled automatic status polling every 3 seconds until task completion

Add polling-based observability UI that shows live task progress:
- getTaskRunStatus: fetch function for GET /api/tasks/runs
- useTaskRunStatus: polling hook (3s interval, stops on terminal state)
- RunLogsList: auto-scrolling log viewer
- RunPage: composed page with status indicator, current step, and logs

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@vercel
Copy link
Copy Markdown
Contributor

vercel bot commented Feb 16, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
recoup-chat Ready Ready Preview Feb 16, 2026 3:49pm

Request Review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Feb 16, 2026

Warning

Rate limit exceeded

@sweetmantech has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 15 minutes and 46 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📝 Walkthrough

Walkthrough

Introduces task run monitoring functionality with a new React hook (useTaskRunStatus) that polls task status via an external API, a new API client module (getTaskRunStatus) with type definitions, a logs display component (RunLogsList), and integrates these into the existing run page with status indicators, logs display, clipboard copy, and error handling.

Changes

Cohort / File(s) Summary
API Client & Types
lib/tasks/getTaskRunStatus.ts
New module defining TaskRunStatus and TaskRunMetadata interfaces, plus getTaskRunStatus function that fetches run data from the Recoup API with authorization and error handling.
React Hook for Polling
hooks/useTaskRunStatus.ts
New hook wrapping the API client with react-query, implementing 3-second polling with auto-refetch disabled in terminal states, 3 retries, and 1-second staleTime.
Logs Display Component
components/TasksPage/Run/RunLogsList.tsx
New component rendering a scrollable list of log strings with auto-scroll-to-bottom on log updates and a placeholder for empty states.
Run Page Integration
components/TasksPage/Run/RunPage.tsx
Enhanced to use useTaskRunStatus hook, displaying status indicators, current step, logs via RunLogsList, clipboard copy for runId with visual feedback, duration, and error details in a card-like layout.

Sequence Diagram(s)

sequenceDiagram
    participant UI as RunPage Component
    participant Hook as useTaskRunStatus Hook
    participant API as getTaskRunStatus Function
    participant External as Recoup API
    
    UI->>Hook: runId provided, component mounts
    Hook->>API: getTaskRunStatus(runId, accessToken)
    API->>External: GET /runs?runId={runId}
    External-->>API: {status, metadata, logs, ...}
    API-->>Hook: TaskRunStatus
    Hook-->>UI: {data, loading, error}
    UI->>UI: Render status, logs, duration
    
    Note over Hook: Poll every 3s if not terminal
    Hook->>API: getTaskRunStatus(runId, accessToken)
    API->>External: GET /runs?runId={runId}
    External-->>API: {status: "complete", ...}
    API-->>Hook: TaskRunStatus
    Hook-->>UI: Updated data (status terminal)
    Hook->>Hook: Disable refetch (terminal reached)
    UI->>UI: Render final state
Loading

Estimated Code Review Effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

📋 Logs now scroll and dance with grace,
As statuses find their rightful place,
The poll tick-tocks through cyberspace,
While tasks run on with steadfast pace,
Copy, complete—a well-paced race! 🚀

🚥 Pre-merge checks | ✅ 1 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Solid & Clean Code ⚠️ Warning Pull request violates SOLID principles: DRY violation with duplicate clipboard logic, magic numbers scattered throughout, SRP violation with RunPage handling multiple concerns, missing accessibility attributes, and logic bugs in error state handling. Extract clipboard logic to useCopy hook, move hardcoded constants to lib/consts.ts, refactor RunPage into focused components, add accessibility attributes (role="log", aria-label), replace array index keys with stable identifiers, and fix error-state logic checking.
✅ Passed checks (1 passed)
Check name Status Explanation
Merge Conflict Detection ✅ Passed ✅ No merge conflicts detected when merging into test

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

✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch sweetmantech/myc-4168-chat-tasksrunid-observability-show-task-logs

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.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 164a794a6c

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

export default function RunPage({ runId }: RunPageProps) {
const { data, isLoading, error } = useTaskRunStatus(runId);

if (isLoading || !data) {
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 Badge Handle query errors before treating missing data as loading

useTaskRunStatus can produce error with data === undefined (for example with an invalid runId, auth failure, or network failure), and this branch runs first because it checks isLoading || !data. In those cases the page stays on the loading spinner forever and never renders the error UI, so users cannot see what went wrong. Evaluate error before the !data fallback (or only show loading when isLoading is true).

Useful? React with 👍 / 👎.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

@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

🤖 Fix all issues with AI agents
In `@components/TasksPage/Run/RunLogsList.tsx`:
- Around line 24-32: The container div in RunLogsList (the element that renders
logs.map and uses bottomRef) should be marked with role="log" for accessibility;
update the outer div in RunLogsList.tsx to include role="log" (leaving bottomRef
and existing className/children unchanged) so screen readers treat it as a live
log region.

In `@components/TasksPage/Run/RunPage.tsx`:
- Around line 34-52: The guard in RunPage currently returns the loading UI when
isLoading || !data, which masks the error state because a failed useQuery yields
isLoading=false but data=undefined; reorder or combine the checks so error is
evaluated before the "no data" loading branch: check error first (the error
variable from the useQuery) and render the error UI if present, then handle
loading/no-data with isLoading || !data; update the conditional logic in the
RunPage component around the existing if blocks (the ones referencing isLoading,
data, and error) accordingly.
🧹 Nitpick comments (5)
components/TasksPage/Run/RunLogsList.tsx (2)

5-7: Export RunLogsListProps per project conventions.

The coding guidelines require exporting component prop types with the ComponentNameProps naming convention. The interface is correctly named but not exported.

-interface RunLogsListProps {
+export interface RunLogsListProps {
   logs: string[];
 }

As per coding guidelines, "Export component prop types with explicit ComponentNameProps naming convention".


12-14: Dependency on logs.length can miss updates where content changes but count stays the same.

If a log entry is replaced rather than appended (unlikely but possible), the effect won't fire. For an append-only log stream this is fine in practice, but using the logs array reference directly would be more robust. React's useEffect performs a shallow comparison, so a new array reference would still trigger the scroll.

   useEffect(() => {
     bottomRef.current?.scrollIntoView({ behavior: "smooth" });
-  }, [logs.length]);
+  }, [logs]);
components/TasksPage/Run/RunPage.tsx (2)

81-81: Unnecessary as string[] type assertion.

data.metadata?.logs is already typed as string[] | undefined via TaskRunMetadata. The ?? [] fallback handles the undefined case, so the assertion adds no safety and could mask a runtime type mismatch if the API returns unexpected data.

-        <RunLogsList logs={logs as string[]} />
+        <RunLogsList logs={logs} />

On line 55, the inferred type of logs from data.metadata?.logs ?? [] is already string[], making the assertion redundant.


93-103: Copy-to-clipboard: consider adding error handling and an accessible label.

navigator.clipboard.writeText can reject (non-secure context, permission denied). A silent catch would prevent an unhandled promise rejection. Additionally, an aria-label would improve accessibility for the copy button.

♻️ Suggested improvement
       <button
-          className="inline-flex cursor-pointer items-center gap-1 hover:text-foreground"
+          aria-label={copied ? "Run ID copied" : "Copy run ID to clipboard"}
+          className="inline-flex cursor-pointer items-center gap-1 hover:text-foreground"
           onClick={() => {
-            navigator.clipboard.writeText(runId);
-            setCopied(true);
-            setTimeout(() => setCopied(false), 2000);
+            navigator.clipboard.writeText(runId).then(() => {
+              setCopied(true);
+              setTimeout(() => setCopied(false), 2000);
+            }).catch(() => {
+              // clipboard write failed silently
+            });
           }}
         >
lib/tasks/getTaskRunStatus.ts (1)

38-42: response.json() can throw on non-JSON error responses.

If the server returns a non-JSON body (e.g., a 502 HTML page from a gateway), response.json() will throw a SyntaxError before you reach the response.ok check. This produces a confusing error message instead of a meaningful one.

🛡️ Defensive fix
-  const data = await response.json();
-
-  if (!response.ok) {
-    throw new Error(data.error || "Failed to fetch task run status");
+  let data: Record<string, unknown>;
+  try {
+    data = await response.json();
+  } catch {
+    throw new Error(`Failed to fetch task run status (HTTP ${response.status})`);
+  }
+
+  if (!response.ok) {
+    throw new Error((data.error as string) || "Failed to fetch task run status");
   }

Comment on lines +24 to +32
return (
<div className="max-h-80 overflow-y-auto rounded-md border bg-muted/30 p-3 font-mono text-sm">
{logs.map((log, i) => (
<p key={i} className="py-0.5 text-muted-foreground">
{log}
</p>
))}
<div ref={bottomRef} />
</div>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Add role="log" for accessibility on the log container.

This is a live-updating log viewer — the role="log" ARIA role is purpose-built for this exact use case. It implies aria-live="polite" and communicates to assistive technology that new content is being appended.

♻️ Proposed fix
-    <div className="max-h-80 overflow-y-auto rounded-md border bg-muted/30 p-3 font-mono text-sm">
+    <div
+      role="log"
+      aria-label="Task run logs"
+      className="max-h-80 overflow-y-auto rounded-md border bg-muted/30 p-3 font-mono text-sm"
+    >

As per coding guidelines, "Use semantic HTML elements appropriate to the component's role" and "Always include accessibility in components: use semantic HTML, ARIA attributes, and keyboard navigation".

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
return (
<div className="max-h-80 overflow-y-auto rounded-md border bg-muted/30 p-3 font-mono text-sm">
{logs.map((log, i) => (
<p key={i} className="py-0.5 text-muted-foreground">
{log}
</p>
))}
<div ref={bottomRef} />
</div>
return (
<div
role="log"
aria-label="Task run logs"
className="max-h-80 overflow-y-auto rounded-md border bg-muted/30 p-3 font-mono text-sm"
>
{logs.map((log, i) => (
<p key={i} className="py-0.5 text-muted-foreground">
{log}
</p>
))}
<div ref={bottomRef} />
</div>
🤖 Prompt for AI Agents
In `@components/TasksPage/Run/RunLogsList.tsx` around lines 24 - 32, The container
div in RunLogsList (the element that renders logs.map and uses bottomRef) should
be marked with role="log" for accessibility; update the outer div in
RunLogsList.tsx to include role="log" (leaving bottomRef and existing
className/children unchanged) so screen readers treat it as a live log region.

Comment on lines +34 to +52
if (isLoading || !data) {
return (
<div className="flex h-screen flex-col items-center justify-center p-4">
<Loader2 className="size-8 animate-spin text-muted-foreground" />
<p className="mt-3 text-sm text-muted-foreground">Loading run status...</p>
</div>
);
}

if (error) {
return (
<div className="flex h-screen flex-col items-center justify-center p-4">
<XCircle className="size-8 text-red-500" />
<p className="mt-3 text-sm text-red-500">
{error instanceof Error ? error.message : "Failed to load run status"}
</p>
</div>
);
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Bug: Error state is unreachable when there's no cached data.

When useQuery fails and there's no prior cached data, isLoading is false but data is undefined. The guard isLoading || !data on line 34 evaluates to true, so the loading spinner is shown — the error check on line 43 is never reached.

Swap the order or combine the conditions:

🐛 Proposed fix
-  if (isLoading || !data) {
+  if (error) {
     return (
-      <div className="flex h-screen flex-col items-center justify-center p-4">
-        <Loader2 className="size-8 animate-spin text-muted-foreground" />
-        <p className="mt-3 text-sm text-muted-foreground">Loading run status...</p>
+      <div className="flex h-screen flex-col items-center justify-center p-4">
+        <XCircle className="size-8 text-red-500" />
+        <p className="mt-3 text-sm text-red-500">
+          {error instanceof Error ? error.message : "Failed to load run status"}
+        </p>
       </div>
     );
   }
 
-  if (error) {
+  if (isLoading || !data) {
     return (
-      <div className="flex h-screen flex-col items-center justify-center p-4">
-        <XCircle className="size-8 text-red-500" />
-        <p className="mt-3 text-sm text-red-500">
-          {error instanceof Error ? error.message : "Failed to load run status"}
-        </p>
+      <div className="flex h-screen flex-col items-center justify-center p-4">
+        <Loader2 className="size-8 animate-spin text-muted-foreground" />
+        <p className="mt-3 text-sm text-muted-foreground">Loading run status...</p>
       </div>
     );
   }
🤖 Prompt for AI Agents
In `@components/TasksPage/Run/RunPage.tsx` around lines 34 - 52, The guard in
RunPage currently returns the loading UI when isLoading || !data, which masks
the error state because a failed useQuery yields isLoading=false but
data=undefined; reorder or combine the checks so error is evaluated before the
"no data" loading branch: check error first (the error variable from the
useQuery) and render the error UI if present, then handle loading/no-data with
isLoading || !data; update the conditional logic in the RunPage component around
the existing if blocks (the ones referencing isLoading, data, and error)
accordingly.

sweetmantech and others added 5 commits February 16, 2026 10:42
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
RunPage now only manages state routing (skeleton, error, loaded).
RunDetails handles all loaded data rendering.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@sweetmantech sweetmantech merged commit 7a5202d into test Feb 16, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant