@wbern/cc-ping
v1.27.0
Published
Ping Claude Code sessions to trigger quota windows early across multiple accounts
Downloads
2,005
Maintainers
Readme
Ping Claude Code sessions to trigger quota windows early across multiple accounts.
Claude Code has a 5-hour quota window that starts on your first message. If you rotate between accounts, your idle accounts sit there with full quota doing nothing. cc-ping pings them so their windows start ticking — when you need them, they've already reset.
Zero telemetry. No data is collected, sent, or phoned home. Everything stays in ~/.config/cc-ping/. The only network activity is the claude CLI call itself, which is subject to Anthropic's normal Claude Code telemetry.
Prerequisites
Claude Code must be installed and on your PATH.
claude --version # verify it's availableInstall
Standalone binary (recommended)
No Node.js required — downloads a single binary:
TAG=$(curl -fsSL https://api.github.com/repos/wbern/cc-ping/releases/latest \
| grep '"tag_name"' | head -1 | cut -d'"' -f4)
curl -fsSL "https://raw.githubusercontent.com/wbern/cc-ping/${TAG}/install.sh" | bash -s -- "$TAG"Installs to ~/.local/bin by default. Override with CC_PING_INSTALL_DIR. You may need to restart your shell or add ~/.local/bin to your PATH.
The two-step form pins the installer script to a published release tag rather than fetching it from main, so a single push to main cannot deliver attacker code to fresh installs. Both the script and the binary it downloads come from the same signed release. Inspect before piping with curl -fsSL "https://raw.githubusercontent.com/wbern/cc-ping/${TAG}/install.sh" | less.
To pin a specific version instead of the latest release:
TAG=v1.21.0
curl -fsSL "https://raw.githubusercontent.com/wbern/cc-ping/${TAG}/install.sh" | bash -s -- "$TAG"Homebrew (macOS / Linux)
brew install wbern/cc-ping/cc-pingOn macOS this builds from source (pulls in bun as a build dependency, ~1
minute on first install) to sidestep Sequoia's Gatekeeper rejecting
downloaded ad-hoc-signed binaries. Linux uses the prebuilt binary.
npm / pnpm
Requires Node.js:
pnpm add -g @wbern/cc-ping
npm install -g @wbern/cc-ping # also worksQuick run (no install)
pnpm dlx @wbern/cc-ping ping # requires pnpm
npx @wbern/cc-ping ping # or npmSetup
Discover accounts from ~ (or a custom directory), then verify they have valid credentials:
cc-ping scan # auto-discover accounts from ~
cc-ping scan /path/to/dir # scan a specific directory
cc-ping check # verify credentials are valid
cc-ping login <handle> # re-authenticate an account that lost its session
cc-ping list # show configured accountsOr add accounts manually:
cc-ping add ~/.claude-accounts/my-accountUsage
cc-ping ping # ping all accounts
cc-ping ping alice bob # ping specific accounts
cc-ping ping --parallel # ping all at once
cc-ping ping --notify # desktop notification on new windows
cc-ping ping --bell # terminal bell on failure
cc-ping ping --stagger 5 # wait 5 min between each account
cc-ping status # show account status table
cc-ping suggest # which account should I use next?
cc-ping next-reset # when does the next window expire?
cc-ping daemon start # auto-ping every 5 hours
cc-ping history # recent ping resultsCommands
cc-ping ping [handles...]
Ping configured accounts to start their quota windows. Pings accounts sequentially by default.
| Flag | Default | Description |
|------|---------|-------------|
| --parallel | false | Ping all accounts simultaneously |
| --stagger <minutes\|auto> | — | Delay between each account (minutes, or auto) |
| --notify | false | Desktop notification on new windows and failures |
| --bell | false | Terminal bell on failure |
| --json | false | Output results as JSON |
| --quiet | false | Suppress per-account output |
| --group <name> | — | Only ping accounts in this group |
cc-ping status
Show all accounts with their quota window state — whether they have an active window, when it resets, whether they're covered by recent Claude Code usage, and duplicate detection.
cc-ping suggest
Recommend which account to use next based on quota window state. Prefers accounts that need pinging or whose windows are about to reset.
cc-ping next-reset
Show which account has its quota window resetting soonest — useful for knowing when capacity frees up.
cc-ping scan
Auto-discover Claude Code accounts. Scans ~ by default, or pass a directory to scan. Each subdirectory containing a .claude.json is detected as an account. Duplicate identities (same accountUuid across directories) are flagged. The default ~ scan skips system and cloud-sync folders (Documents, Pictures, Google Drive, Dropbox, …) to avoid triggering macOS permission prompts; an explicitly provided directory is searched verbatim.
cc-ping check
Verify that each configured account's config directory exists and has credentials.
cc-ping login [handle]
Re-authenticate accounts through the official claude auth login OAuth flow — useful after a ping reports auth expired. The browser/device flow runs interactively with no timeout, scoped to each account's CLAUDE_CONFIG_DIR, and prefills the account's email. Credentials are isolated per config directory, so logging into one account never disturbs another's session.
When a ping reports an expired or missing session — either an HTTP 401 or a "Not logged in" result — cc-ping records that account as needing login. Running cc-ping login with no handle logs in all flagged accounts in turn — it prints how many need login, then runs each interactive flow sequentially (the next begins only after the previous one exits). A successful login (or a later successful ping) clears the flag. Pass a handle to re-auth one specific account regardless of its flag. cc-ping status also surfaces a "needs login" hint for flagged accounts.
cc-ping add <config-dir>
Manually add an account by config directory path. The handle defaults to the directory name — override with --name.
cc-ping remove <handle>
Remove an account from the configuration. Also clears the account's last-ping state (timestamp and metadata). Ping history in history.jsonl is preserved as an audit log.
cc-ping cleanup
Remove orphan state entries for handles that are no longer in the configuration — useful after account renames or manual config edits. Use --dry-run to preview without writing. Supports --json for scripted use. Does not touch history.jsonl.
cc-ping list
List all configured accounts with their config directory paths.
cc-ping history
Show recent ping results — handle, success/failure, duration, cost.
cc-ping schedule reset [handle]
Reset smart scheduling data to recompute optimal ping times. Pass a handle to reset a specific account, or omit it to reset all accounts.
cc-ping completions <shell>
Generate shell completion scripts for bash, zsh, or fish.
cc-ping moo
Send a test notification to verify notifications work on your platform. Fires a desktop notification, and — if a remote URL is configured — a remote (phone) push too.
cc-ping notify set [topic]
Set up remote phone notifications via ntfy.sh (or a compatible server). Omit the topic to have a secure, hard-to-guess one generated for you; the command then prints the next steps (install the app, subscribe, test). The topic is the credential, so treat it as a secret. See Remote notifications.
| Flag | Default | Description |
|------|---------|-------------|
| --server <url> | https://ntfy.sh | ntfy-compatible server base URL (for self-hosting) |
cc-ping notify set-url <url>— advanced: set the full HTTPS push URL directlycc-ping notify show— show whether remote notifications are configured (URL is masked)cc-ping notify clear-url— disable remote notifications
Daemon
The daemon auto-pings on a schedule so you don't have to remember.
cc-ping daemon start # every 5 hours (default)
cc-ping daemon status # check if running, next ping time
cc-ping daemon stop # graceful shutdown| Flag | Default | Description |
|------|---------|-------------|
| --interval <minutes> | 300 | Ping interval in minutes (300 = 5h quota window) |
| --smart-schedule <on\|off> | on | Time pings based on your usage patterns |
| --notify | false | Desktop notification on new windows and failures |
| --bell | false | Terminal bell on failure |
| --quiet | true | Suppress per-account output in logs |
The daemon is smart about what it pings:
- Skips active windows — accounts with a quota window still running are skipped to avoid wasting pings
- Detects recent usage — if you've been using Claude Code directly, the account already has an active window. The daemon detects this from Claude Code's activity timestamps and skips the ping
- Retries failures — if any accounts fail to ping, the daemon retries only the failed ones before sleeping
- Backs off until a rate limit lifts — when a ping comes back rate-limited (HTTP 429), cc-ping reads the reset time from the response (
resets 9pm) and, if every pending failure is a rate limit, sleeps until just after that reset instead of retrying every few minutes. The log line names the limit type and reset (weekly limit (resets 9pm)) - Detects system sleep — if the machine wakes from sleep and a ping cycle is overdue, the daemon notices and factors the delay into notifications
- Singleton enforcement — only one daemon runs at a time, verified by PID and process name
- Graceful shutdown —
daemon stopwrites a sentinel file and waits up to 60s for a clean exit before force-killing - Auto-restart on upgrade — after upgrading cc-ping, the daemon detects the binary has changed and exits so the service manager can restart it with the new version.
daemon statuswarns if the running daemon is outdated - Self-healing — the daemon emits a heartbeat while idle; an installed watchdog restarts it if the heartbeat goes stale (e.g. an event-loop stall after laptop sleep/wake)
Logs are written to ~/.config/cc-ping/daemon.log.
Smart scheduling
By default, the daemon analyzes your Claude Code usage history to time pings optimally. The goal: your 5-hour quota window expires right when you're most active, not while you're asleep.
Your typical day:
12am 6am 12pm 6pm 12am
| | ________|________ | |
. . . . . | coding time | . . . .
^
peak activity
Fixed interval -- ping fires whenever the timer says:
[======= 5h window =======]
12am 5am
^ expires while you sleep
Smart scheduling -- ping timed so window expires at peak:
[======= 5h window =======]
8am 1pm
^ expires while you code!How it works:
- Reads Claude Code's
history.jsonlfrom each account's config directory (prompt timestamps written by the Claude CLI — not to be confused with cc-ping's ownhistory.jsonl) - Builds an hour-of-day histogram from the last 14 days
- Slides a 5-hour window across the histogram to find the densest period
- Schedules pings so the window expires at the midpoint of peak activity
Defer zone: When smart scheduling is active, pings that would fire in the 5 hours before the optimal time are deferred. Pings outside this zone proceed normally for continuous coverage.
Fallback: If an account has fewer than 7 days of history or a flat usage pattern (no clear peak), smart scheduling is skipped and the fixed interval is used instead.
To disable: cc-ping daemon start --smart-schedule off
System service (survive reboots)
daemon start runs as a detached process that won't survive a reboot. Use daemon install to register as a system service that starts automatically on login:
cc-ping daemon install --notify # install and start
cc-ping daemon status # shows "System service: installed"
cc-ping daemon uninstall # remove service and stop| Platform | Service manager | Service file |
|----------|----------------|--------------|
| macOS | launchd | ~/Library/LaunchAgents/com.cc-ping.daemon.plist |
| Linux | systemd (user) | ~/.config/systemd/user/cc-ping-daemon.service |
The service restarts the daemon on crash (but not on clean exit via daemon stop). No sudo required — both use user-level service managers.
daemon stop vs daemon uninstall: When a service is installed, daemon stop kills the process but the service manager may restart it on crash. Use daemon uninstall to fully remove the service, or daemon stop if you just need a temporary pause.
Watchdog (self-healing)
daemon install also registers a lightweight watchdog that runs every two minutes as a separate, short-lived process. It exists to recover from a rare failure mode where the runtime's event loop wedges and pegs a CPU core — most often after a laptop sleeps/wakes at low battery (a Bun / libuv clock-desync bug). When the loop wedges, timers stop firing, so the daemon can't recover itself — but it also stops refreshing its heartbeat file, which is exactly what the watchdog keys off.
| Platform | Mechanism | Files |
|----------|-----------|-------|
| macOS | launchd StartInterval agent | ~/Library/LaunchAgents/com.cc-ping.watchdog.plist |
| Linux | systemd .timer + oneshot .service | ~/.config/systemd/user/cc-ping-watchdog.{timer,service} |
The check is deliberately conservative: it force-restarts the daemon only when a recorded daemon process is alive and its heartbeat is more than three minutes stale. A missing heartbeat (a daemon that predates this feature, or one that just restarted) is treated as healthy and left alone. Worst case is a single harmless restart; it can never leave the daemon hung. Watchdog activity is logged to ~/.config/cc-ping/watchdog.log.
Upgrading from an earlier version? The watchdog is added by
daemon install. After upgrading, re-runcc-ping daemon install(oruninstalltheninstall) to register it —daemon statuswill remind you if it's missing. The daemon binary upgrades and runs fine without it; you just don't get automatic recovery until the watchdog is installed.
Notifications
Desktop notifications work on macOS, Linux, and Windows:
| Platform | Mechanism |
|----------|-----------|
| macOS | osascript (AppleScript display notification) |
| Linux | notify-send |
| Windows | PowerShell New-BurntToastNotification |
Use cc-ping moo to verify notifications work on your system.
Remote notifications
For alerts that reach your phone when you're away from your desk, cc-ping can POST to an ntfy.sh-compatible push endpoint (genuine iOS + Android push, no signup, self-hostable). This is especially useful for a headless daemon: it surfaces the high-value events — a ping failure or an account that needs re-login — even with no desktop attached.
cc-ping notify set # generates a secure topic and prints the setup steps
cc-ping moo # send a test push once you've subscribed in the appnotify set walks you through it: it picks a hard-to-guess topic (or use your own with cc-ping notify set my-topic), saves it, and tells you to install the ntfy app and subscribe to that topic. Self-hosting? Point at your own server with --server https://ntfy.example.com. Notes:
- The URL must be HTTPS, and the topic string is the credential — anyone who knows it can read your alerts, so keep it private. cc-ping masks the URL in
notify showand never logs it. - Remote notifications fire independently of
--notify, so the daemon pushes to your phone whether or not desktop notifications are enabled. - Delivery is best-effort with a short timeout and a couple of retries on transient errors; a failed push is logged and never affects pinging. A foreground
cc-ping pingwaits for the push to send, but never longer than a few seconds. - Events. By default you're pushed for
failureandnew-window. A rate limit is its ownrate-limitedevent that is off by default — it's expected and self-resolving, so it would otherwise nag you (aweekly limit (resets 9pm)push) on every retry. To opt in, set theeventsarray in~/.config/cc-ping/config.json, e.g."remoteNotify": { "url": "…", "events": ["failure", "new-window", "rate-limited"] }. - Disable any time with
cc-ping notify clear-url.
Using a different app (Discord, Telegram, Slack, email, …): cc-ping speaks ntfy's wire format (a plain-text body with Title/Priority headers), so pointing it straight at a raw Discord/Slack/Telegram webhook won't work — those expect their own payloads. Instead, send to ntfy and let a gateway fan out: ntfy itself can forward to other services, or run Apprise / shoutrrr as a bridge and point cc-ping notify set-url at it. Self-hosting ntfy also gives you access tokens and ACLs if a hard-to-guess topic isn't enough.
Shell completions
# bash
cc-ping completions bash >> ~/.bashrc
# zsh
cc-ping completions zsh >> ~/.zshrc
# fish
cc-ping completions fish > ~/.config/fish/completions/cc-ping.fishHow it works
Each ping spawns the claude CLI with a trivial arithmetic prompt:
claude -p "Quick, take a guess: what is 2847 + 6192?" \
--output-format json \
--tools "" \
--max-turns 1The account is selected by setting CLAUDE_CONFIG_DIR to the account's config directory, so the claude CLI authenticates as that account.
Key design choices:
- Arithmetic prompts — random math questions minimize token usage (~150 input tokens, ~10 output). Templates and operands are randomized to avoid cache hits across pings.
- Tools disabled —
--tools ""prevents the model from doing anything beyond answering the question. - Single turn —
--max-turns 1ensures one request-response cycle, no follow-ups. - 30s timeout with hard kill — pings that take longer are sent SIGKILL. A backstop timer force-resolves the promise even if the child process doesn't exit cleanly.
- Cost tracking — each ping records its USD cost and token usage so you can audit spend.
After a successful ping, the account's last-ping timestamp is saved to ~/.config/cc-ping/state.json. The 5-hour quota window is calculated from this timestamp — commands like status, suggest, and the daemon all use it to determine window state.
Privacy
cc-ping sends zero telemetry. No analytics, no tracking, no phoning home.
All data stays local in ~/.config/cc-ping/:
| File | Contents |
|------|----------|
| config.json | Account names and config directory paths |
| state.json | Last ping timestamp and cost metadata per account |
| history.jsonl | Ping history (timestamp, handle, success/failure, duration) |
| daemon.json | Daemon PID, interval, start time |
| daemon.log | Daemon output log |
The only network activity is the claude CLI call itself, which communicates with Anthropic's API under their standard terms and privacy policy. cc-ping does not intercept, modify, or inspect this traffic beyond reading the JSON response for cost metadata.
License
MIT
