diff --git a/README.md b/README.md index 0609bc7..105f81e 100644 --- a/README.md +++ b/README.md @@ -144,8 +144,41 @@ If you use NEEDLE for LLM fleet dispatch, `install.sh` automatically copies `cla - **One prompt per invocation** — there is no multi-turn session mode; each call starts a fresh session. - **Startup latency ~2–5s** — the PTY handshake and Claude Code startup add overhead versus a direct HTTP call. +## Troubleshooting + +### Billing classification verification + +Before deploying to production, verify that sessions are billing against the subscription pool (`cc_entrypoint=cli`): + +```bash +# Check the most recent session's billing classification +./scripts/check-billing.sh +``` + +This script inspects the latest transcript JSONL under `~/.claude/projects/` and asserts the `entrypoint` field is `"cli"` (subscription), not `"sdk-cli"` (credit pool). Exit 0 means correct billing; exit 1 means a billing regression. Run this after every release or Claude Code upgrade. + +### Common issues + +**PTY open failed** — You may be in a container without `/dev/ptmx`. Run on a bare-metal host or a VM with full PTY support. + +**Session never completes** — The Stop hook may not be firing. Check `--verbose` output for "Stop received" and verify your `~/.claude/settings.json` isn't blocking hook execution. + +**Empty output despite success** — The transcript reader may have hit a race condition. Run with `--verbose` to see retry attempts; if retries exceed 40×50ms, the Stop hook fired before the JSONL was flushed. + +## Release checklist + +Before cutting a release tag: + +1. Run `./scripts/check-billing.sh` to verify billing conformance (requires credentials) +2. Run `cargo test` to ensure all mocked tests pass +3. Run `claude-print --check` to verify PTY and Stop hook mechanics +4. Update version in `Cargo.toml` +5. Commit and push: `git tag v0.x.y && git push origin v0.x.y` +6. Monitor the `claude-print-ci` Argo Workflow for successful build and GitHub release + ## Structure - `docs/notes/` — design decisions, constraints, integration details - `docs/plan/plan.md` — complete implementation plan -# +- `scripts/check-billing.sh` — AS-4 billing conformance script (run before every release) +- `scripts/` — integration test scripts diff --git a/scripts/check-billing.sh b/scripts/check-billing.sh new file mode 100755 index 0000000..cfd2ddd --- /dev/null +++ b/scripts/check-billing.sh @@ -0,0 +1,79 @@ +#!/bin/bash +# check-billing.sh - AS-4 billing conformance script +# Inspects the most recent transcript JSONL under ~/.claude/projects/ and asserts +# the session has entrypoint 'cli' (subscription pool), not 'sdk-cli'. +# This is the AS-4 pre-release gate - run before every release. + +set -eu + +# ANSI color codes for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' + +log_info() { + echo -e "${GREEN}[INFO]${NC} $1" +} + +log_error() { + echo -e "${RED}[ERROR]${NC} $1" >&2 +} + +log_warn() { + echo -e "${YELLOW}[WARN]${NC} $1" >&2 +} + +# Find the newest JSONL file under ~/.claude/projects/ +TRANSCRIPTS_DIR="$HOME/.claude/projects" + +if [ ! -d "$TRANSCRIPTS_DIR" ]; then + log_error "Claude projects directory not found: $TRANSCRIPTS_DIR" + log_error "Has claude or claude-print been run on this machine?" + exit 1 +fi + +# Find the most recently modified .jsonl file (recursive) +NEWEST_JSONL=$(find "$TRANSCRIPTS_DIR" -type f -name "*.jsonl" -printf '%T@ %p\n' 2>/dev/null | sort -rn | head -1 | cut -d' ' -f2-) + +if [ -z "$NEWEST_JSONL" ]; then + log_error "No transcript JSONL files found under: $TRANSCRIPTS_DIR" + log_error "Run claude or claude-print first to generate a transcript." + exit 1 +fi + +log_info "Inspecting most recent transcript: $NEWEST_JSONL" + +# Scan for the entrypoint field in the JSONL +# We use jq if available, fallback to grep+sed +ENTRYPOINT= + +if command -v jq >/dev/null 2>&1; then + # Use jq for robust JSON parsing + ENTRYPOINT=$(grep -m1 '"entrypoint"' "$NEWEST_JSONL" | jq -r '.entrypoint // empty' 2>/dev/null || echo "") +else + # Fallback: extract with grep and sed + # Matches: "entrypoint": "cli" or "entrypoint":"cli" + ENTRYPOINT=$(grep -m1 '"entrypoint"' "$NEWEST_JSONL" | sed -n 's/.*"entrypoint"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' 2>/dev/null || echo "") +fi + +if [ -z "$ENTRYPOINT" ]; then + log_error "No entrypoint field found in transcript: $NEWEST_JSONL" + log_error "The transcript may be from an incompatible Claude Code version." + exit 1 +fi + +# Print the entrypoint value +echo "entrypoint: $ENTRYPOINT" + +# Assert it equals 'cli' +if [ "$ENTRYPOINT" = "cli" ]; then + log_info "Billing classification: SUBSCRIPTION (cli) - PASS" + exit 0 +else + log_error "Billing classification: AGENT SDK CREDIT POOL ($ENTRYPOINT) - FAIL" + log_error "Expected: cli (subscription pool)" + log_error "Actual: $ENTRYPOINT" + log_error "File inspected: $NEWEST_JSONL" + exit 1 +fi