Skip to content

[luv-297] feat: multi-harness session log download#298

Merged
NiveditJain merged 4 commits intomainfrom
luv-297
May 5, 2026
Merged

[luv-297] feat: multi-harness session log download#298
NiveditJain merged 4 commits intomainfrom
luv-297

Conversation

@NiveditJain
Copy link
Copy Markdown
Member

@NiveditJain NiveditJain commented May 5, 2026

Summary

  • Dashboard log export now works for every harness, not just Claude Code. The Download Logs button on the session page was gated behind !isExternal, and the API route at app/api/download/[project]/[session]/route.ts only resolved Claude paths. New lib/download-session.ts dispatches per-CLI through the existing find*Transcript(sessionId) helpers and streams the original on-disk JSONL byte-for-byte for Codex / Copilot / Cursor / Pi / Gemini (in addition to Claude). The route validates ?cli=<id> against isKnownCli, validates sessionId per-CLI shape (UUID for six, ^ses_[A-Za-z0-9]+$ for opencode), and logActivity now records the originating CLI.
  • OpenCode export preserves the SQLite structure rather than collapsing parts into per-message lines. New getOpenCodeSessionExport(sessionId) returns {session, messages, parts} with the JSON data columns parsed; the route emits it as application/json with a <sessionId>.json filename. Malformed JSON in data survives as the raw string.
  • Tests: __tests__/lib/download-session.test.ts (12 cases — happy path + 404 for all 7 CLIs, 400 on bad cli / malformed session id, real on-disk fixture for codex), __tests__/api/download-route.test.ts (6 route-level cases including the back-compat default of cli=claude), and 4 new cases in __tests__/lib/opencode-sessions.test.ts covering getOpenCodeSessionExport.
  • Docs touched: docs/dashboard.mdx (the user-facing description of the Download button) and docs/architecture.mdx (the ascii-art directory tree comment for the route).

Test plan

  • bun run lint clean (1 pre-existing warning unrelated to this change).
  • bunx tsc --noEmit clean.
  • bun run test:run — all 1511 unit tests across 70 files pass.
  • bun run test:e2e — all 290 e2e tests across 13 files pass.
  • bun run build — Next + dist build succeed.
  • Local smoke against bun run dev:
    • /api/download/__/<uuid>?cli=codex against a real ~/.codex/sessions/.../<uuid>.jsonl — HTTP 200, byte-identical to source (174312 bytes diff'd).
    • /api/download/<projectFolder>/<uuid> (no ?cli) against a real Claude transcript — HTTP 200, byte-identical (back-compat).
    • /api/download/__/<sessionId>?cli=opencode against a real OpenCode session — HTTP 200, application/json, Content-Disposition: ...filename="<sessionId>.json", body parses to {session, messages, parts} with parsed data columns.
    • /api/download/__/garbage?cli=codex → HTTP 400 (malformed sessionId).
    • /api/download/__/<uuid>?cli=bogus → HTTP 400 (unknown cli).
    • /api/download/__/<uuid>?cli=codex (no transcript on disk) → HTTP 404.

🤖 Generated with Claude Code

Summary by CodeRabbit

Release Notes v0.0.10-beta.0

  • New Features

    • Added per-CLI Download Logs capability; OpenCode sessions now export as JSON documents while other CLIs export original transcripts.
    • Extended transcript export support across multiple CLI environments.
  • Bug Fixes

    • Fixed session log viewer layout issues.
    • Improved transcript path resolution and session tracking across dashboards.
  • Documentation

    • Updated export/download functionality guidance and architecture documentation.

NiveditJain and others added 2 commits May 5, 2026 13:40
Generalises `app/api/download/[project]/[session]/route.ts` from a
Claude-only file streamer to a per-CLI dispatcher in `lib/download-session.ts`,
and drops the `!isExternal` gate on the session-page Download Logs button.

The five external file-backed CLIs (Codex, Copilot, Cursor, Pi, Gemini)
already had per-CLI session loaders that discover an on-disk JSONL via
`find*Transcript(sessionId)`; the dispatcher streams those bytes verbatim
so replay tooling sees the original CLI shape.

OpenCode keeps sessions in SQLite (~/.local/share/opencode/opencode.db)
across three tables (session/message/part), so a flat JSONL would lose
the part rows. New `getOpenCodeSessionExport(sessionId)` returns the full
SQLite snapshot and the dispatcher emits it as `application/json` with the
JSON `data` columns parsed for readability — preserving the underlying
structure rather than the viewer's collapsed-to-message shape.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…g export

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

coderabbitai Bot commented May 5, 2026

Warning

Rate limit exceeded

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

To keep reviews running without waiting, you can enable usage-based add-on for your organization. This allows additional reviews beyond the hourly cap. Account admins can enable it under billing.

⌛ 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.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: ddc6fd6b-dff6-4f33-a60c-e063a8ce047b

📥 Commits

Reviewing files that changed from the base of the PR and between 68358a7 and 76e85a7.

📒 Files selected for processing (3)
  • CHANGELOG.md
  • __tests__/lib/opencode-sessions.test.ts
  • lib/opencode-sessions.ts
📝 Walkthrough

Walkthrough

This PR introduces a per-CLI Download Logs feature for the dashboard. It adds a dispatcher (lib/download-session.ts) that validates session IDs and resolves download sources across multiple CLI backends, returning either file paths or synthesized JSON for OpenCode. The route handler, UI, and comprehensive test coverage are updated accordingly.

Changes

Download Logs Feature

Layer / File(s) Summary
Type & Export Definitions
lib/download-session.ts, lib/opencode-sessions.ts
DownloadSource union type defined for file-backed and synthesized responses; OpenCodeSessionExportData interface added to shape exported session/messages/parts; isValidSessionId() and isKnownCli() validators introduced.
Core Download Dispatcher
lib/download-session.ts
resolveDownloadSource() routes CLI-specific resolution across Claude, Codex, Copilot, Cursor, Pi, Gemini (all file-backed) and OpenCode (synthesized JSON), with path traversal protection and exhaustive CLI handling.
OpenCode Export Implementation
lib/opencode-sessions.ts
getOpenCodeSessionExport() fetches session/messages/parts from SQLite, parses data fields, and returns a structured export object or null for missing sessions.
Download Route Handler
app/api/download/[project]/[session]/route.ts
GET handler updated to accept cli query param, validate via isKnownCli(), resolve source via resolveDownloadSource(), and stream file-backed or respond with synthesized body; handles 400/404/500 error cases.
UI Integration
app/project/[name]/session/[sessionId]/page.tsx
Download Logs block always renders (removes !isExternal gate) and includes cli query parameter in download URL.
Tests
__tests__/api/download-route.test.ts, __tests__/lib/download-session.test.ts, __tests__/lib/opencode-sessions.test.ts
Comprehensive Vitest suites covering unknown CLI errors, session validation, file streaming, OpenCode export payloads, 404/500 paths, path traversal rejection, and end-to-end fixture tests.
Documentation
docs/architecture.mdx, docs/dashboard.mdx, CHANGELOG.md
API route architecture documented; export behavior clarified by backend type (file-backed CLIs vs. SQLite OpenCode); release notes detail feature and fixes.

Sequence Diagram

sequenceDiagram
    participant Client as Browser / Client
    participant Page as app/project/[name]/<br>session/[sessionId]/page.tsx
    participant Route as app/api/download/<br>[project]/[session]/route.ts
    participant Dispatcher as lib/download-<br>session.ts
    participant Export as lib/opencode-<br>sessions.ts
    participant FS as File System /<br>SQLite DB

    Client->>Page: Load session page
    Page->>Client: Render Download Logs button<br>(with cli param)
    Client->>Route: GET /api/download/proj/ses?cli=opencode
    Route->>Dispatcher: isKnownCli('opencode')?
    Dispatcher-->>Route: true
    Route->>Dispatcher: resolveDownloadSource('opencode', 'proj', 'ses')
    Dispatcher->>Export: getOpenCodeSessionExport('ses')
    Export->>FS: Query SQLite for session/messages/parts
    FS-->>Export: { session, messages[], parts[] }
    Export-->>Dispatcher: OpenCodeSessionExportData
    Dispatcher-->>Route: { kind: 'synthesized', body, contentType, extension }
    Route->>Client: 200 + JSON body<br>Content-Type: application/json<br>Content-Disposition: attachment

    Client->>Route: GET /api/download/proj/uuid?cli=codex
    Route->>Dispatcher: resolveDownloadSource('codex', 'proj', 'uuid')
    Dispatcher->>FS: Locate transcript file
    FS-->>Dispatcher: /path/to/transcript.jsonl
    Dispatcher-->>Route: { kind: 'file', path }
    Route->>FS: Stream file
    FS-->>Route: File content
    Route->>Client: 200 + streamed JSONL<br>Content-Type: application/x-ndjson
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related issues

  • Issue #279: This PR directly implements the per-CLI "Download Logs" backend and UI feature requested—adding the dispatcher, OpenCode export capability, route updates, and removing external-only gating.

Possibly related PRs

  • exospherehost/failproofai#270: Both PRs extend OpenCode and Pi session handling; this PR builds on that integration with export and download dispatch logic for the same CLIs.
  • exospherehost/failproofai#294: Both PRs modify transcript-resolution and synthetic session-marker dispatch (opencode-db:// and per-CLI paths used in download flow).
  • exospherehost/failproofai#284: Both PRs improve Pi session-id handling and ensure dashboard session links resolve correctly across CLI backends.

Poem

🐰 Logs flow free in every way,
Claude, Codex, OpenCode display,
Download streams or JSON bright,
Each CLI gets its format right!
No more gating, all can share,
Session wisdom, bright and fair.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 66.67% 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 PR title '[luv-297] feat: multi-harness session log download' clearly and concisely summarizes the main change: enabling session log download for multiple CLI harnesses, not just Claude Code.
Description check ✅ Passed The PR description is comprehensive and well-structured, covering the main changes, new functions, test cases, and documentation updates. However, it does not follow the required template with explicit checkboxes for Type of Change and the Checklist sections.
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.


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

NiveditJain added a commit that referenced this pull request May 5, 2026
#296)

* [luv-298] fix: name the offending subcommand in block-work-on-main deny message

The deny formatter ran a second, looser regex that captured the first
git subcommand in chained commands, so a blocked
`git checkout -b feat/x && git commit -m "y"` would render as
"Git checkout on main is blocked" — misleading users into thinking the
policy blocks branch creation. It does not. Reuse the existing
GIT_COMMIT_MERGE_RE capture so the message always names the actual
commit/merge/rebase/cherry-pick offender.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* [luv-298] docs: correct block-work-on-main description

The English doc said the policy "denies checking out main or master
branches directly" and listed `protectedBranches` as branch names
that cannot be checked out — both wrong, and the same misconception
that prompted the deny-message fix in the previous commit. The policy
denies commit/merge/rebase/cherry-pick on protected branches, not
checkout. Update the doc to reflect actual behavior.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* [luv-298] chore: correct PR number in changelog entry

Original entry referenced #298 (the branch name); the actual PR is #296.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
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: 1

🧹 Nitpick comments (1)
__tests__/lib/opencode-sessions.test.ts (1)

176-210: ⚡ Quick win

Add one regression case for export query failure behavior.

Please add a test where the session query succeeds but message/part query fails, and assert getOpenCodeSessionExport() returns null (to avoid silently treating DB errors as empty export data).

✅ Suggested test case
 describe("getOpenCodeSessionExport", () => {
+  it("returns null when a downstream export query fails after session lookup", async () => {
+    let callCount = 0;
+    mockExec.mockImplementation(() => {
+      callCount++;
+      if (callCount === 1) {
+        return JSON.stringify([
+          { id: "ses_x", project_id: "p1", slug: null, directory: "/repo", title: "T", time_created: 1, time_updated: 2 },
+        ]);
+      }
+      throw new Error("db locked");
+    });
+    await expect(getOpenCodeSessionExport("ses_x")).resolves.toBeNull();
+  });

As per coding guidelines, **/*.ts: Always add unit tests in __tests__/ for new behavior; never modify existing tests just to make them pass.

🤖 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 `@__tests__/lib/opencode-sessions.test.ts` around lines 176 - 210, Add a
regression test inside the describe("getOpenCodeSessionExport") suite that
simulates a successful session query but a failing messages/parts query and
asserts getOpenCodeSessionExport(...) returns null; use the existing test
helpers (mockQueries and mockExec) to return a session row for the first query
and make the second query throw or return an error-like response, then call
getOpenCodeSessionExport("ses_x") and expect null to ensure DB errors for
messages/parts are not treated as empty exports.
🤖 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 `@lib/opencode-sessions.ts`:
- Around line 263-268: The current code calls runOpenCodeDb for messages and
parts and uses "?? []" to convert null (query failure) into an empty array,
which hides DB errors and yields partial/empty exports; replace the "?? []"
behavior with explicit null-checks that throw or propagate an error including
the sessionId and which query failed (e.g., after running
runOpenCodeDb<OpenCodeMessageRow>(...) and runOpenCodeDb<OpenCodePartRow>(...),
if the result is null throw new Error(`Failed to load messages for session
${sessionId}`) and similarly for parts) so failures surface instead of silently
returning empty arrays.

---

Nitpick comments:
In `@__tests__/lib/opencode-sessions.test.ts`:
- Around line 176-210: Add a regression test inside the
describe("getOpenCodeSessionExport") suite that simulates a successful session
query but a failing messages/parts query and asserts
getOpenCodeSessionExport(...) returns null; use the existing test helpers
(mockQueries and mockExec) to return a session row for the first query and make
the second query throw or return an error-like response, then call
getOpenCodeSessionExport("ses_x") and expect null to ensure DB errors for
messages/parts are not treated as empty exports.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 5b8256d6-5e5f-42cf-b7c0-9c04b4612ff6

📥 Commits

Reviewing files that changed from the base of the PR and between 5ac5312 and 68358a7.

📒 Files selected for processing (10)
  • CHANGELOG.md
  • __tests__/api/download-route.test.ts
  • __tests__/lib/download-session.test.ts
  • __tests__/lib/opencode-sessions.test.ts
  • app/api/download/[project]/[session]/route.ts
  • app/project/[name]/session/[sessionId]/page.tsx
  • docs/architecture.mdx
  • docs/dashboard.mdx
  • lib/download-session.ts
  • lib/opencode-sessions.ts

Comment thread lib/opencode-sessions.ts Outdated
NiveditJain and others added 2 commits May 5, 2026 13:50
Per CodeRabbit on PR #298: `getOpenCodeSessionExport` was coalescing
`runOpenCodeDb` failures (which return `null`) to `[]` via `?? []`,
making a broken DB call indistinguishable from a genuinely empty
session in the export. Honest 404 is better than a misleading 200
with `messages: []` / `parts: []`. New unit test pins the behaviour
when the message/part queries error mid-flight.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@NiveditJain NiveditJain merged commit 4321d06 into main May 5, 2026
9 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