Skip to content

Instantly share code, notes, and snippets.

@lark1115
Created March 19, 2026 03:39
Show Gist options
  • Select an option

  • Save lark1115/311f38eb11e872ea95c6fd4548988e5f to your computer and use it in GitHub Desktop.

Select an option

Save lark1115/311f38eb11e872ea95c6fd4548988e5f to your computer and use it in GitHub Desktop.
session-from-r2: Cloudflare R2 から Claude Code の過去セッションログを検索・復元・要約するスキル
name session-from-r2
description 過去のセッションログ (R2) を読み込んで、セッションコンテキストを復元する。 Cloudflare R2 に保存された JSONL 形式のセッションログを取得・要約する。 トリガー: /session-from-r2、「前回の引き継ぎ読んで」「ハンドオーバー確認して」 「前回どこまでやった?」「前のセッションの続きから」 引数でセッション ID を直接指定可能。省略時は R2 から自動検索。
allowed-tools
Read
Bash

Session from R2

過去のセッションログ (R2) を読み込んで、セッションコンテキストを復元する。

R2 アクセスには sops-secrets 経由の S3 互換クレデンシャルが必要。 session-to-r2 と同じ認証方式 (curl + S3v4 署名) を使用する。 wrangler は不要。

環境変数

変数 必須 説明
SESSION_R2_BUCKET Yes R2 バケット名 (default: claude-sessions)
R2_S3_ACCESS_KEY_ID Yes R2 S3 Access Key ID
R2_S3_SECRET_ACCESS_KEY Yes R2 S3 Secret Access Key
R2_S3_ENDPOINT Yes R2 S3 endpoint URL

sops-secrets でロード済みであれば自動で利用可能。

Procedure

Minimize commands. Each R2 API call has latency — never fetch the same object twice.

Step 1: Load credentials

# sops-secrets から R2 クレデンシャルをロード (未設定の場合)
if [ -z "${R2_S3_ACCESS_KEY_ID:-}" ]; then
  SOPS_FILE=""
  for _candidate in \
    "$(git rev-parse --show-toplevel 2>/dev/null)/secrets/agents.enc.env" \
    "$HOME/.agents/skills/secrets/agents.enc.env" \
  ; do
    [ -f "$_candidate" ] && SOPS_FILE="$_candidate" && break
  done
  unset _candidate
  if [ -n "$SOPS_FILE" ] && command -v sops &>/dev/null; then
    export SOPS_AGE_KEY_FILE="${SOPS_AGE_KEY_FILE:-$HOME/.config/sops/age/keys.txt}"
    while IFS='=' read -r k v; do [ -n "$k" ] && export "$k=$v"; done \
      < <(sops decrypt --input-type dotenv --output-type dotenv "$SOPS_FILE" 2>/dev/null)
  fi
fi
export SESSION_R2_BUCKET="${SESSION_R2_BUCKET:-claude-sessions}"

Step 2: Find the target session

If a session ID is given as argument, skip to Step 3.

Otherwise, discover available sessions:

REPO_PREFIX=$(git remote get-url origin 2>/dev/null | sed -E 's#.*[:/]([^/]+)/([^/.]+)(\.git)?$#\1/\2#')

# R2 S3 API で session 一覧を取得
~/.agents/scripts/r2-s3v4.sh list "$SESSION_R2_BUCKET" "$REPO_PREFIX/" | \
  python3 -c "
import sys, json
from collections import defaultdict
data = json.loads(sys.stdin.read())
sessions = defaultdict(list)
for obj in data.get('Contents', []):
    key = obj['Key']
    parts = key.split('/')
    if len(parts) >= 3:
        session_id = parts[2]
        sessions[session_id].append(obj)
for sid, objs in sorted(sessions.items(),
    key=lambda x: max(o['Key'] for o in x[1]), reverse=True)[:10]:
    latest = max(o['Key'] for o in objs)
    total_size = sum(int(o.get('Size', 0)) for o in objs)
    print(f'{sid}  chunks={len(objs)}  size={total_size//1024}KB  latest={latest.split(\"/\")[-1]}')
"

Step 3: Read the latest chunk

SESSION_ID="<selected-session-id>"

# 最新チャンクのキーを取得
LATEST_KEY=$(~/.agents/scripts/r2-s3v4.sh list "$SESSION_R2_BUCKET" "$REPO_PREFIX/$SESSION_ID/" | \
  python3 -c "
import sys, json
data = json.loads(sys.stdin.read())
keys = sorted([o['Key'] for o in data.get('Contents', [])])
if keys: print(keys[-1])
")

# ダウンロード
~/.agents/scripts/r2-s3v4.sh get "$SESSION_R2_BUCKET" "$LATEST_KEY" /tmp/session-chunk.jsonl

# パース・要約
python3 -c '
import sys, json
first_ts = last_ts = None
user_msgs, asst_msgs = [], []
for line in open("/tmp/session-chunk.jsonl"):
    try: obj = json.loads(line)
    except: continue
    ts = obj.get("timestamp", "")
    if not first_ts and ts: first_ts = ts
    if ts: last_ts = ts
    role, msg = obj.get("type", ""), obj.get("message", {})
    if not isinstance(msg, dict): continue
    content = msg.get("content", "")
    if isinstance(content, list):
        content = " ".join(c.get("text", "") for c in content if isinstance(c, dict) and c.get("type") == "text")
    if not content: continue
    if role == "user" and len(content) < 500:
        user_msgs.append((ts[11:19] if len(ts) > 19 else ts, content[:200]))
    elif role == "assistant" and len(content) > 20:
        asst_msgs.append((ts[11:19] if len(ts) > 19 else ts, content[:300]))
print(f"Range: {first_ts} -> {last_ts}")
print(f"Events: {len(user_msgs)} user, {len(asst_msgs)} assistant")
print("--- User messages ---")
for ts, m in user_msgs: print(f"[{ts}] {m}")
print("--- Key assistant messages (last 15) ---")
for ts, m in asst_msgs[-15:]: print(f"[{ts}] {m}")
'

Step 4: Summarize + offer to go deeper

From the latest chunk output, report:

  • Chunk info and total chunk count
  • What was done, key decisions, issues, outcomes
  • Any incomplete tasks or next steps

Then ask: if the user needs more context from earlier in the session, fetch additional chunks (work backwards from the second-to-last chunk). Do not pre-fetch all chunks.

Key Rules

  • Never fetch the same R2 object more than once.
  • Always read the latest chunk by default.
  • Keep the summary concise — the user can ask for details.
  • Focus on assistant text messages (user messages often contain verbose tool results).
  • Report the session date and time range.
  • If the session references files, verify they still exist before suggesting actions.
  • Clean up temp files: rm -f /tmp/session-chunk.jsonl after reading.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment