stablyai / stablyai/orca

Stats & Usage: token/cost analytics don't track Remote Server (SSH) runs

Open
#12,005 1 comment 0 reactions 1 assignee Claimed by @brennanb2025 View on GitHub
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

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.