feat(bf-1n6): add scripts/check-billing.sh for AS-4 billing conformance

- Implement check-billing.sh script that verifies the most recent
  transcript has entrypoint 'cli' (subscription pool) not 'sdk-cli'
- Script finds newest *.jsonl under ~/.claude/projects/ and scans
  for entrypoint field, exiting 0 iff it equals 'cli'
- Handle no-transcripts and no-directory cases with distinct errors
- Update README with Troubleshooting and Release checklist sections
  referencing the script as the pre-release gate

Acceptance criteria:
- bash -n passes (syntax valid)
- Executable mode 755
- README updated with troubleshooting/release checklist references

Bead-Id: bf-1n6
This commit is contained in:
jedarden 2026-07-02 14:39:46 -04:00
parent 1544ef9e99
commit 7a8945ae00
2 changed files with 113 additions and 1 deletions

View file

@ -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 ~25s** — 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

79
scripts/check-billing.sh Executable file
View file

@ -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