Stats & Usage: token/cost analytics don't track Remote Server (SSH) runs
- Dominant language
- TypeScript
- Stars
- 71.3k
- Forks
- 4.7k
- Avg merge
- 14h 54m
- Merged PRs (30d)
- 520
Description
## Summary
The **Stats & Usage** analytics (tokens, cost, per-model, sessions) for Claude/Codex/OpenCode do not track runs that happen on a **Remote Server** (SSH target). Only local agent runs are counted. The "Fun Stats" (agents spawned, time worked, PRs) already work remotely because they are driven by the PTY data stream, but the token/cost analytics are local-only file scrapers.
## Root cause
The usage scanners read agent CLI artifacts from the **local** filesystem only:
- `src/main/claude-usage/scanner.ts:48-49` — `CLAUDE_PROJECTS_DIR` / `CLAUDE_TRANSCRIPTS_DIR` are derived from `homedir()`.
- `src/main/codex/codex-home-paths.ts` — Codex homes are all `homedir()`/userData-derived.
- `src/main/opencode-usage/scanner.ts` — local XDG only.
When an agent runs on a Remote Server over SSH, those artifacts are written on the **remote host** (`~/.claude/projects/...` on the server), so the desktop never sees them. The maintainers already acknowledge this explicitly:
- `src/main/automations/run-usage-collection.ts:69-75` returns `remote_usage_unavailable` for any `executionTargetType === 'ssh'` automation.
- UI copy in `UsageOverviewPane.tsx` only mentions "local agent logs".
## Why it's fixable without relay changes
For SSH remotes, Orca already:
- Deploys a thin `relay.js` and wires a full `SshFilesystemProvider` (`readDir`, `stat`, `readFile`, `realpath`) keyed by `connectionId`.
- Exposes `getActiveSshAiVaultHostInfo(targetId)` → `{ remoteHome, hostPlatform, executionHostId }`.
- Reads remote Claude/Codex transcripts **today** via `scanRemoteAiVaultSessions` (`src/main/ipc/ai-vault.ts:240` → `src/main/ai-vault/remote-session-scanner.ts`) using exactly this pattern.
So the same primitives can scan remote usage logs over the existing SSH connection — no relay changes, no redeployment, no wire-handshake bump.
## Proposed approach (MVP: Claude via SSH-filesystem bridge)
1. Introduce a `UsageFilesystem` abstraction (`readDir` / `stat` / `readLines` / `realpath`) with a local `node:fs` implementation and an SSH implementation backed by `SshFilesystemProvider`.
2. Refactor `scanClaudeUsageFiles` to accept an optional scan context (filesystem + roots) while staying fully backward-compatible (local default + existing `homedir()` roots).
3. Add `scanClaudeUsageFilesRemote({ provider, remoteHome, hostPlatform, worktrees, previous })` building remote roots via `joinRemotePath(hostPlatform, remoteHome, '.claude', 'projects'|'transcripts')`.
4. Extract the store query builders into pure functions and add `buildClaudeUsageSnapshotFromScan(...)` so a remote scan result can be rendered as a `ClaudeUsageSnapshot` without local persistence.
5. Wire an IPC `claudeUsage:scanRemote({ connectionId, scope, range })` + preload bridge + a remote-host selector in the Claude usage pane.
Codex/OpenCode remote scanning would follow the same pattern as a follow-up.
## Acceptance
- Selecting a connected SSH target in Stats & Usage → Claude tab shows token/cost/model analytics computed from that remote server's `~/.claude` transcripts.
- Existing local scanning behavior and tests are unchanged.
- Remote scanning degrades gracefully when the connection drops (same `SSH_FILESYSTEM_PROVIDER_UNAVAILABLE_MESSAGE` pattern as AI Vault).
Contributor guide
Assessment
This issue has not been assessed yet.