DEV Community

Cover image for I Built a Local Cost Monitor for Claude Code Using Just Bash and SQLite
Edward Kubiak
Edward Kubiak

Posted on

I Built a Local Cost Monitor for Claude Code Using Just Bash and SQLite

If you've been using Claude Code heavily, you've probably had this moment: you open
your Anthropic billing page and wonder when exactly that happened. Which session?
Which agent? Which project?

There's no built-in answer. Claude Code doesn't expose per-session cost data, and the
billing dashboard shows you totals — not the story behind them.

So I built cast-observe: a local observability layer that hooks into Claude Code's
event lifecycle and writes everything to a SQLite file on your machine.

What You Get

  • Per-session token counts and USD cost
  • Agent run history (name, status, duration, cost)
  • Daily and weekly cost summaries, filterable by project
  • Budget alerts when you cross a threshold
  • A live TUI dashboard (cast-observe dash)
  • Direct SQL access to all your data

No cloud. No telemetry. No SaaS. Just ~/.claude/cast.db.


How It Works

Claude Code supports a hook system — shell scripts that fire on lifecycle events like
SessionStart, SubagentStop, PostToolUse, etc. cast-observe registers eight of them:

Hook What It Does
SessionStart Opens a new session row in SQLite
SessionEnd Finalizes the session, triggers budget checks
SubagentStart Records an agent invocation
SubagentStop Logs completion status, duration, and cost
PostToolUse Reads token usage from the tool response, computes USD
PostToolUseFailure Same — failed calls still cost tokens
PreCompact / PostCompact Handles Claude's context compaction events

All hooks run with async: true so they never block Claude Code execution.

The Data Flow

Every PostToolUse fires with a JSON payload on stdin containing the model name, input
tokens, and output tokens. A small Python script looks up the model's price per million
tokens from a local pricing config, computes the cost, and writes a row to SQLite with
retry logic to handle lock contention.

# Simplified — see observe-cost-tracker.py for full version
cost = (input_tokens / 1_000_000 * input_price) + \
       (output_tokens / 1_000_000 * output_price)
Enter fullscreen mode Exit fullscreen mode

The hook reads from stdin, never from environment variables. This keeps sensitive
session data out of the process table.

The Schema

Four tables:

  • sessions — one row per Claude Code session, with aggregated tokens and cost
  • agent_runs — one row per agent invocation (name, status, duration, cost)
  • routing_events — placeholder for CAST multi-agent routing data
  • budgets — your configured limits and alert thresholds

The CLI

# Today's usage and recent agent runs
cast-observe status

# Budget summaries
cast-observe budget
cast-observe budget --week
cast-observe budget --project my-project

# Session history
cast-observe sessions --limit 20

# Raw SQL access
cast-observe db query "SELECT agent_name, SUM(cost_usd) FROM agent_runs GROUP BY agent_name"

# Launch the TUI
cast-observe dash
Enter fullscreen mode Exit fullscreen mode

The TUI is built with Textual and shows live agent
runs, session cost breakdown, and status colors (green = DONE, yellow = DONE_WITH_CONCERNS,
red = BLOCKED).


Installation

Via Homebrew:

brew tap ek33450505/cast-observe
brew install cast-observe
cast-observe install
Enter fullscreen mode Exit fullscreen mode

From source:

git clone https://github.com/ek33450505/cast-observe.git
cd cast-observe
bash install.sh
Enter fullscreen mode Exit fullscreen mode

The install script merges the hook configuration non-destructively into your existing
~/.claude/settings.json, so it won't clobber any hooks you already have.

Requirements: macOS 12+ or Linux, Claude Code, python3, sqlite3.


Why Local SQLite?

Observability tools tend to default to "send it to a server." I wanted the opposite.

  • Zero latency — writes are local, no network round trips on every tool call
  • Full ownership — your usage data stays on your machine
  • SQL as the API — power users can query anything directly
  • Works offline — no outage pages, no rate limits

The WAL (Write-Ahead Logging) mode keeps concurrent reads and writes from blocking
each other, which matters because multiple hooks can fire in rapid succession during
an agent run.


Works Alongside CAST

If you use CAST — the multi-agent
framework built on top of Claude Code — cast-observe shares the same ~/.claude/cast.db
database. CAST writes to routing_events; cast-observe reads from it but doesn't own it.
Installing one later won't break the other.


What's Next

  • Per-model cost breakdown in the TUI
  • Exportable reports (CSV/JSON)
  • Session diff view: compare two runs side by side
  • GitHub Actions integration for tracking CI-time usage

The repo is at ek33450505/cast-observe,
MIT licensed. PRs, issues, and feedback welcome.

If you're using Claude Code and have been flying blind on cost, give it a try.

Top comments (0)