claude -pをsystemdタイマーで定期実行すると、気づかないうちにClaude ProやMaxの利用制限を消費してしまいます。- Anthropicの非公式APIエンドポイントからusageを取得できるので、それをラップした
claude-usageコマンドを作りました。 - 単純な使用率の閾値ではなく、残り使用量と残り時間の比率を比較することで、消費ペースが速すぎるときだけ停止する判定ロジックを実装しています。
- このガードをスクリプト冒頭に入れることで、肝心なときに制限に当たる問題を防ぎます。
1. claudeを定期実行していたら利用制限を消費していた
claude -p はClaude Codeを非対話モードで呼び出すフラグです1。
ただ、systemdタイマーで定期実行すると、気づかないうちに利用制限を消費してしまいます。
Claude ProやMaxには5時間ウィンドウと7日間ウィンドウの制限があり2、残りusageが少ないのに自動処理が走り続けると、肝心なときに使えなくなります。
ここでは、利用状況を取得して「このペースだと制限に当たる」と判断したときにタイマーを自動停止する仕組みを紹介します。
1.1. 利用状況はAPIから取得できる
Claude Codeの /status コマンドが表示するusageは、以下の非公式なエンドポイントから取得できました3。
GET https://api.anthropic.com/api/oauth/usage
Authorization: Bearer <OAuthトークン>
anthropic-beta: oauth-2025-04-20
Code language: HTML, XML (xml)
レスポンスはこのような構造です。
{
"five_hour": {
"utilization": 37.0,
"resets_at": "2026-03-05T09:00:00.000000+00:00"
},
"seven_day": {
"utilization": 26.0,
"resets_at": "2026-03-06T04:00:00.000000+00:00"
}
}
Code language: JSON / JSON with Comments (json)
utilization が消費済みの割合で、resets_at がウィンドウのリセット時刻です。
ただ、このエンドポイントは非公式のため、Claude Codeのバージョンアップで仕様が変わる可能性があります。
動かなくなった場合はトークン期限切れかエンドポイント変更が原因として考えられます。claude-usage がエラーを返したら claude を一度起動してトークンをリフレッシュするか、Claude Codeのアップデートを確認してみてください。
また、/api/oauth/usage 自体にもレート制限があり、短時間に連続アクセスすると429が返ることがあります。

1.2. OAuthトークンの場所(.claudeAiOauth.accessToken)
このエンドポイントはAPIキーではなく、Claude Codeがログイン時に取得するOAuthトークンを使います。
トークンはClaude Codeでの認証時に自動で保存するので、こちらで用意する必要はありません4。
- Linux / WSL:
~/.claude/.credentials.jsonの.claudeAiOauth.accessToken - macOS: Keychain の
Claude Code-credentialsエントリ
トークンには有効期限があり、Claude Codeが対話セッションを起動するたびにリフレッシュされます。claude -p のみで運用している環境では期限切れになることがあるので、その場合は claude を一度対話起動して再ログインしてください。
2. claude-usage コマンドを作った
上記APIをラップした claude-usage コマンドを ~/.local/bin/ に置いて使うことにしました。

オプションの設計はこうなっています。
| オプション | 出力 |
|---|---|
| なし | JSON(utilization%) |
--remain | JSON(remaining%) |
--5hour | 5hのutilization%(数値のみ) |
--1week | 7dayのutilization%(数値のみ) |
--5hour -remain | 5hのremaining%(数値のみ) |
--5hour --time-rate | 5hのリセットまでの残り時間%(数値のみ) |
--5hour --time-rate -remain | 5hの経過時間%(数値のみ) |
--time-rate はリセットまでの残り時間をウィンドウ全体で割って100倍した値です。
ウィンドウ開始直後は100に近く、リセット直前は0に近くなります。-r を組み合わせると反転して経過時間の割合になります。
試しに確認していたら、APIのレート制限に引っかかってしまって応答がなくなってしまったので、これを避けるため、レスポンスを120秒間キャッシュするようにしましした。
正常取得時はログにも記録するので、使い方のパターンを後から確認できます。
claude-usage
# {
# "five_hour": { "utilization": 14.0, "resets_at": "..." },
# "seven_day": { "utilization": 81.0, "resets_at": "..." }
# }
claude-usage --5hour -r
# 86.0
claude-usage --5hour --time-rate
# 40.0
Code language: PHP (php)
2.1. claude-usage スクリプト全文
#!/bin/bash
# claude-usage.sh
#
# 使い方:
# claude-usage # JSON出力(utilization%)
# claude-usage --5hour # 5hのutilization%(数値のみ)
# claude-usage --1week # 7dayのutilization%(数値のみ)
# claude-usage --remain # JSON出力(remaining%)
# claude-usage --5hour --remain # 5hのremaining%(数値のみ)
# claude-usage --1week --remain # 7dayのremaining%(数値のみ)
# claude-usage --5hour --time-rate # 5hのリセットまでの残り時間比率(%)
# claude-usage --1week --time-rate # 7dayのリセットまでの残り時間比率(%)
#
# 該当ウィンドウのデータがない場合はすべて 0 として扱う。
# APIレスポンスは CACHE_TTL_SEC 秒間キャッシュする。正常取得時はログにも記録する。
set -euo pipefail
readonly FIVE_HOUR_WINDOW_SEC=18000
readonly SEVEN_DAY_WINDOW_SEC=604800
readonly CACHE_FILE="${HOME}/.cache/claude-usage/response.json"
readonly LOG_FILE="${HOME}/.cache/claude-usage/history.log"
readonly CACHE_TTL_SEC=120
# ── キャッシュ ────────────────────────────────────────
is_cache_valid() {
[[ -f "$CACHE_FILE" ]] || return 1
local mtime now age
mtime=$(stat -c %Y "$CACHE_FILE" 2>/dev/null || stat -f %m "$CACHE_FILE" 2>/dev/null)
now=$(date +%s)
age=$(( now - mtime ))
(( age < CACHE_TTL_SEC ))
}
load_cache() { cat "$CACHE_FILE"; }
save_cache() {
mkdir -p "$(dirname "$CACHE_FILE")"
echo "$1" > "$CACHE_FILE"
}
append_log() {
local body="$1"
mkdir -p "$(dirname "$LOG_FILE")"
local five_util five_resets seven_util seven_resets
five_util=$(echo "$body" | jq -r '.five_hour.utilization // 0')
five_resets=$(echo "$body" | jq -r '.five_hour.resets_at // "null"')
seven_util=$(echo "$body" | jq -r '.seven_day.utilization // 0')
seven_resets=$(echo "$body" | jq -r '.seven_day.resets_at // "null"')
echo "$(date '+%Y-%m-%dT%H:%M:%S') 5h=${five_util}% resets_at=${five_resets} 7d=${seven_util}% resets_at=${seven_resets}" >> "$LOG_FILE"
}
# ── トークン取得 ──────────────────────────────────────
get_token() {
local creds_file="$HOME/.claude/.credentials.json"
local token=""
if [[ -f "$creds_file" ]]; then
token=$(jq -r '.claudeAiOauth.accessToken // empty' "$creds_file" 2>/dev/null || true)
fi
if [[ -z "$token" ]] && command -v security &>/dev/null; then
local creds
creds=$(security find-generic-password -s "Claude Code-credentials" -w 2>/dev/null || true)
token=$(echo "$creds" | jq -r '.claudeAiOauth.accessToken // empty' 2>/dev/null || true)
fi
echo "$token"
}
# ── API取得 ───────────────────────────────────────────
fetch_usage() {
local token="$1"
local response http_code body
response=$(curl -s --max-time 10 -w "\n%{http_code}" \
"https://api.anthropic.com/api/oauth/usage" \
-H "Authorization: Bearer $token" \
-H "anthropic-beta: oauth-2025-04-20" \
-H "Content-Type: application/json")
http_code=$(echo "$response" | tail -1)
body=$(echo "$response" | head -n -1)
if [[ "$http_code" != "200" ]]; then
echo "API error ($http_code): $body" >&2
return 1
fi
echo "$body"
}
# ── 計算 ─────────────────────────────────────────────
to_remaining() {
echo "scale=1; 100 - $1" | bc
}
time_remain_rate() {
local resets_at="$1" window_sec="$2"
if [[ -z "$resets_at" || "$resets_at" == "null" ]]; then
echo "0.0"; return
fi
local reset_epoch now_epoch remaining
reset_epoch=$(date -d "$resets_at" +%s 2>/dev/null \
|| date -jf "%Y-%m-%dT%H:%M:%S" "${resets_at%%.*}" +%s 2>/dev/null \
|| echo 0)
if [[ "$reset_epoch" == "0" ]]; then
echo "failed to parse resets_at: $resets_at" >&2
return 1
fi
now_epoch=$(date +%s)
remaining=$(( reset_epoch - now_epoch ))
if (( remaining < 0 )); then remaining=0; fi
echo "scale=1; $remaining / $window_sec * 100" | bc
}
# ── 出力 ─────────────────────────────────────────────
output_single() {
local utilization="$1" resets_at="$2" window_sec="$3" remain="$4" time_rate="$5"
if [[ "$time_rate" == "1" && "$remain" == "1" ]]; then
local rate
rate=$(time_remain_rate "$resets_at" "$window_sec")
echo "scale=1; 100 - $rate" | bc
elif [[ "$time_rate" == "1" ]]; then
time_remain_rate "$resets_at" "$window_sec"
elif [[ "$remain" == "1" ]]; then
to_remaining "$utilization"
else
echo "$utilization"
fi
}
output_json() {
local five_util="$1" five_resets="$2" seven_util="$3" seven_resets="$4" remain="$5"
local five_val seven_val key
if [[ "$remain" == "1" ]]; then
five_val=$(to_remaining "$five_util")
seven_val=$(to_remaining "$seven_util")
key="remaining_pct"
else
five_val="$five_util"
seven_val="$seven_util"
key="utilization"
fi
jq -n \
--arg key "$key" \
--argjson five_val "$five_val" \
--arg five_resets "$five_resets" \
--argjson seven_val "$seven_val" \
--arg seven_resets "$seven_resets" \
'{
five_hour: { ($key): $five_val, resets_at: $five_resets },
seven_day: { ($key): $seven_val, resets_at: $seven_resets }
}'
}
# ── メイン ────────────────────────────────────────────
main() {
local window="" remain="0" time_rate="0"
for arg in "$@"; do
case "$arg" in
--5hour)
[[ -n "$window" ]] && { echo "cannot combine --5hour and --1week" >&2; exit 1; }
window="5h"
;;
--1week)
[[ -n "$window" ]] && { echo "cannot combine --5hour and --1week" >&2; exit 1; }
window="week"
;;
--remain) remain="1" ;;
--time-rate) time_rate="1" ;;
*) echo "Usage: $0 [--5hour|--1week] [--remain] [--time-rate]" >&2; exit 1 ;;
esac
done
local token
token=$(get_token)
[[ -z "$token" ]] && { echo "トークンが取得できません" >&2; exit 1; }
local usage
if is_cache_valid; then
usage=$(load_cache)
else
usage=$(fetch_usage "$token") || exit 1
save_cache "$usage"
append_log "$usage"
fi
local five_util five_resets seven_util seven_resets
five_util=$(echo "$usage" | jq -r '.five_hour.utilization // 0')
five_resets=$(echo "$usage" | jq -r '.five_hour.resets_at // "null"')
seven_util=$(echo "$usage" | jq -r '.seven_day.utilization // 0')
seven_resets=$(echo "$usage" | jq -r '.seven_day.resets_at // "null"')
case "$window" in
5h) output_single "$five_util" "$five_resets" "$FIVE_HOUR_WINDOW_SEC" "$remain" "$time_rate" ;;
week) output_single "$seven_util" "$seven_resets" "$SEVEN_DAY_WINDOW_SEC" "$remain" "$time_rate" ;;
"") output_json "$five_util" "$five_resets" "$seven_util" "$seven_resets" "$remain" ;;
esac
}
main "$@"
Code language: PHP (php)
2.2. インストール
cp claude-usage.sh ~/.local/bin/claude-usage
chmod +x ~/.local/bin/claude-usage
# PATH に ~/.local/bin が含まれていることを確認
echo $PATH | grep -q "$HOME/.local/bin" || echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.bashrc
Code language: PHP (php)
2.3. claudeのアクセストークンのリフレッシュ
使っていたら、アクセストークンが切れていることが多くありました。
このとき、claude codeを起動するだけでも、とくに再認証がなくトークンがリフレッシュされます。
API error (401): {“type”:”error”,”error”:{“type”:”authentication_error”,”message”:”OAuth token has expired. Please obtain a new token or refresh your existing token.”,”details”:{“error_visibility”:”user_facing”,”error_code”:”token_expired”}},”request_id”:”req_〜”}

スクリプトで実行するには、標準入力で/exitを与えることで、claude codeを起動してすぐに終了できます。
echo "/exit" | claudeCode language: PHP (php)
3. 停止判定の考え方
単純に「usageが80%を超えたら停止」としてもよいですが、残り時間と組み合わせて判定するようにしました。
たとえばusageが70%消費済みでも、残り時間が5%しかないなら続ける意味は薄いです。
逆にusageが50%でも残り時間が10%なら、残り時間に対して消費ペースが速すぎると判断できます。
そこで次の比較式を使います。
remaining_usage% < remaining_time% × MARGIN_FACTOR のとき停止
remaining_usage% は 100 - utilization、remaining_time% は claude-usage --5hour --time-rate の値です。MARGIN_FACTOR を1.0にするとぴったりのペース管理になり、1.2〜1.5にすると早めに停止します。
3.1. ガードスクリプト
systemdタイマーから呼ぶスクリプトの冒頭にこのガードを入れます。
#!/bin/bash
set -euo pipefail
readonly MARGIN_FACTOR="1.2"
check_usage() {
local window="$1"
local window_flag="--${window/5h/5hour}"
window_flag="${window_flag/week/1week}"
local remaining_usage remaining_time threshold
remaining_usage=$(claude-usage "$window_flag" -r)
remaining_time=$(claude-usage "$window_flag" --time-rate)
threshold=$(echo "scale=4; $remaining_time * $MARGIN_FACTOR" | bc)
if (( $(echo "$remaining_usage < $threshold" | bc -l) )); then
echo "${window}: 残りusage=${remaining_usage}% 残り時間=${remaining_time}% 閾値=${threshold}% 停止します" >&2
return 1
fi
}
if check_usage "5h" && check_usage "week"; then
claude -p "なんかの処理"
fiCode language: JavaScript (javascript)-pは--printの略で、以前は「headless mode」と呼ばれていました。現在の公式ドキュメントでは「CLI mode」として説明されています。CI/CDパイプラインやシェルスクリプトからの呼び出しに適しており、--output-format jsonなどのオプションと組み合わせることで出力を構造化できます。 – Run Claude Code programmatically – Claude Code Docs- 5時間ウィンドウは固定時刻ではなく、最初にリクエストを送った時点から計算されるローリングウィンドウです。そのためリセット時刻は毎日変わります。7日間ウィンドウは2025年8月28日から導入された制限で、24時間365日バックグラウンドで使い続けるユーザーへの対応として設けられました。 – When Does Claude Code Usage Reset? – Usagebar Blog
- このエンドポイントはAnthropicの公式APIドキュメントには記載されていません。Claude Codeの
/statusコマンドの通信をモニタリングして発見したものです。仕様は予告なく変更される可能性があります。 – Claude Code Limit Reached – Understanding Lockouts and Recovery | ClaudeLog - LinuxではJSONファイルとして平文で保存されます。macOSではKeychainに保存されるため、
security find-generic-passwordコマンドで取得できます。このトークンはclaude.aiのOAuth認証フローで発行されるもので、APIキー(sk-ant-で始まる文字列)とは別物です。 – Extra usage for paid Claude plans | Claude Help Center