claude -p の利用制限を気にしつつ
自動実行する
(api/oauth/usage)

  • claude -pをsystemdタイマーで定期実行すると、気づかないうちにClaude ProやMaxの利用制限を消費してしまいます。
  • Anthropicの非公式APIエンドポイントからusageを取得できるので、それをラップしたclaude-usageコマンドを作りました。
  • 単純な使用率の閾値ではなく、残り使用量と残り時間の比率を比較することで、消費ペースが速すぎるときだけ停止する判定ロジックを実装しています。
  • このガードをスクリプト冒頭に入れることで、肝心なときに制限に当たる問題を防ぎます。

関連記事

1. claudeを定期実行していたら利用制限を消費していた

claude -p はClaude Codeを非対話モードで呼び出すフラグです1

claude -p の自動実行と利用制限 問題 定期実行 96% 制限消費 気づかず使い果たす 5時間・7日ウィンドウ制限 肝心な時に使えなくなる 解決策 API 使用量取得 自動停止 消費ペースを監視 残量 < 閾値 → 停止 制限を使い切らずに済む vs

ただ、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.1. 利用状況はAPIから取得できる

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/ に置いて使うことにしました。

2. claude-usage コマンドを作った
claude-usage コマンド ~/.local/bin/claude-usage $ claude-usage → JSON { five_hour, seven_day } $ claude-usage –5hour -r → 残り使用量% (数値のみ) $ claude-usage –5hour –time-rate → リセットまでの残り時間% 使用量取得 OAuth トークン利用 非公式 API エンドポイント キャッシュ 120秒間レスポンス保持 429 レート制限を回避 ログ記録 取得時に history.log へ 使用パターンを確認可能

オプションの設計はこうなっています。

オプション出力
なしJSON(utilization%)
--remainJSON(remaining%)
--5hour5hのutilization%(数値のみ)
--1week7dayのutilization%(数値のみ)
--5hour -remain5hのremaining%(数値のみ)
--5hour --time-rate5hのリセットまでの残り時間%(数値のみ)
--5hour --time-rate -remain5hの経過時間%(数値のみ)

--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_〜”}

2.3. claudeのアクセストークンのリフレッシュ

スクリプトで実行するには、標準入力で/exitを与えることで、claude codeを起動してすぐに終了できます。

echo "/exit" | claudeCode language: PHP (php)

3. 停止判定の考え方

単純に「usageが80%を超えたら停止」としてもよいですが、残り時間と組み合わせて判定するようにしました。

停止判定の考え方 remaining_usage% < remaining_time% × MARGIN この条件を満たしたとき → タイマー停止 ✓ 続行 usage 70% 消費済み 残り時間 60% remaining: 30% threshold: 60% × 1.2 = 72% 30 > 72 → 続行 ✗ 停止 usage 70% 消費済み 残り時間 5% remaining: 30% threshold: 5% × 1.2 = 6% 30 > 6 → 続行… ではなく停止

たとえばusageが70%消費済みでも、残り時間が5%しかないなら続ける意味は薄いです。
逆にusageが50%でも残り時間が10%なら、残り時間に対して消費ペースが速すぎると判断できます。

そこで次の比較式を使います。

remaining_usage% < remaining_time% × MARGIN_FACTOR のとき停止

remaining_usage%100 - utilizationremaining_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)
  1. -p--print の略で、以前は「headless mode」と呼ばれていました。現在の公式ドキュメントでは「CLI mode」として説明されています。CI/CDパイプラインやシェルスクリプトからの呼び出しに適しており、--output-format json などのオプションと組み合わせることで出力を構造化できます。 – Run Claude Code programmatically – Claude Code Docs
  2. 5時間ウィンドウは固定時刻ではなく、最初にリクエストを送った時点から計算されるローリングウィンドウです。そのためリセット時刻は毎日変わります。7日間ウィンドウは2025年8月28日から導入された制限で、24時間365日バックグラウンドで使い続けるユーザーへの対応として設けられました。 – When Does Claude Code Usage Reset? – Usagebar Blog
  3. このエンドポイントはAnthropicの公式APIドキュメントには記載されていません。Claude Codeの /status コマンドの通信をモニタリングして発見したものです。仕様は予告なく変更される可能性があります。 – Claude Code Limit Reached – Understanding Lockouts and Recovery | ClaudeLog
  4. LinuxではJSONファイルとして平文で保存されます。macOSではKeychainに保存されるため、security find-generic-password コマンドで取得できます。このトークンはclaude.aiのOAuth認証フローで発行されるもので、APIキー(sk-ant-で始まる文字列)とは別物です。 – Extra usage for paid Claude plans | Claude Help Center