Last active
April 20, 2026 10:09
-
-
Save lukehinds/275325a85d5649f18cbc3a187e5a52d5 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env bash | |
| # Capture a real Claude auth/login/renewal failure without depending on other | |
| # repo-local helper scripts. | |
| # | |
| # This script is intended to be publishable as a single standalone artifact. | |
| set -euo pipefail | |
| usage() { | |
| cat <<'EOF' | |
| Usage: | |
| ./capture-live-claude-auth-failure.sh [options] | |
| Options: | |
| --label TEXT | |
| Human-friendly label for the capture bundle. | |
| --nono-bin PATH | |
| nono executable to use. | |
| Default: `command -v nono` | |
| --claude-bin PATH | |
| Claude executable to use. | |
| Default: `command -v claude` | |
| --output-dir DIR | |
| Parent directory for output bundles. | |
| Default: ${TMPDIR:-/tmp}/nono-live-claude-auth-failure | |
| --claude-config-dir DIR | |
| Treat this as the active CLAUDE_CONFIG_DIR for snapshots and repro. | |
| --note TEXT | |
| Add a note to the bundle. May be repeated. | |
| --help, -h | |
| Show this help. | |
| What it does: | |
| 1. Captures current environment/auth metadata | |
| 2. Runs one controlled startup probe under nono | |
| 3. Captures before/after auth-state and file snapshots | |
| 4. Archives the whole bundle as a tar.gz | |
| When Claude launches: | |
| - reproduce the auth failure once | |
| - do not try multiple different fixes | |
| - once the outcome is clear, exit Claude | |
| EOF | |
| } | |
| fail() { | |
| echo "Error: $*" >&2 | |
| exit 1 | |
| } | |
| timestamp() { | |
| date '+%Y-%m-%d %H:%M:%S' | |
| } | |
| log() { | |
| printf '[%s] %s\n' "$(timestamp)" "$*" | tee -a "$RUN_LOG" | |
| } | |
| sanitize_label() { | |
| printf '%s' "$1" | tr -cs 'A-Za-z0-9._-' '-' | |
| } | |
| detect_realpath() { | |
| local path="$1" | |
| if command -v realpath >/dev/null 2>&1; then | |
| realpath "$path" | |
| return | |
| fi | |
| python3 - <<'PY' "$path" | |
| import os | |
| import sys | |
| print(os.path.realpath(sys.argv[1])) | |
| PY | |
| } | |
| run_and_capture() { | |
| local outfile="$1" | |
| shift | |
| { | |
| printf '$' | |
| for arg in "$@"; do | |
| printf ' %q' "$arg" | |
| done | |
| printf '\n' | |
| "$@" 2>&1 | |
| } >"$outfile" | |
| } | |
| run_interactive_and_capture() { | |
| local outfile="$1" | |
| shift | |
| local cmd_file | |
| cmd_file="$(mktemp "${TMPDIR:-/tmp}/nono-live-auth-cmd.XXXXXX")" | |
| { | |
| printf '#!/bin/sh\n' | |
| printf 'exec' | |
| for arg in "$@"; do | |
| printf ' %q' "$arg" | |
| done | |
| printf '\n' | |
| } >"$cmd_file" | |
| chmod 700 "$cmd_file" | |
| set +e | |
| if command -v script >/dev/null 2>&1; then | |
| script -q "$outfile" "$cmd_file" | |
| local cmd_rc=$? | |
| else | |
| "$cmd_file" >"$outfile" 2>&1 | |
| local cmd_rc=$? | |
| fi | |
| set -e | |
| printf '\n[exit-code] %s\n' "$cmd_rc" >>"$outfile" | |
| rm -f "$cmd_file" | |
| return "$cmd_rc" | |
| } | |
| write_header_file() { | |
| local outfile="$1" | |
| shift | |
| printf '%s\n' "$@" >"$outfile" | |
| } | |
| is_truthy() { | |
| local value | |
| value="$(printf '%s' "${1:-}" | tr '[:upper:]' '[:lower:]')" | |
| case "$value" in | |
| 1|true|yes|on) | |
| return 0 | |
| ;; | |
| *) | |
| return 1 | |
| ;; | |
| esac | |
| } | |
| is_macos() { | |
| [[ "$(uname -s)" == "Darwin" ]] | |
| } | |
| hash_prefix() { | |
| local input="$1" | |
| if command -v shasum >/dev/null 2>&1; then | |
| printf '%s' "$input" | shasum -a 256 | awk '{print substr($1, 1, 8)}' | |
| return | |
| fi | |
| if command -v sha256sum >/dev/null 2>&1; then | |
| printf '%s' "$input" | sha256sum | awk '{print substr($1, 1, 8)}' | |
| return | |
| fi | |
| perl -MDigest::SHA=sha256_hex -e ' | |
| use strict; | |
| use warnings; | |
| local $/; | |
| my $value = <STDIN>; | |
| print substr(sha256_hex($value // q{}), 0, 8); | |
| ' | |
| } | |
| oauth_file_suffix() { | |
| if [[ -n "${CLAUDE_CODE_CUSTOM_OAUTH_URL:-}" ]]; then | |
| printf '%s' '-custom-oauth' | |
| return | |
| fi | |
| if [[ "${USER_TYPE:-}" == "ant" ]]; then | |
| if is_truthy "${USE_LOCAL_OAUTH:-}"; then | |
| printf '%s' '-local-oauth' | |
| return | |
| fi | |
| if is_truthy "${USE_STAGING_OAUTH:-}"; then | |
| printf '%s' '-staging-oauth' | |
| return | |
| fi | |
| fi | |
| printf '%s' '' | |
| } | |
| get_username() { | |
| if [[ -n "${USER:-}" ]]; then | |
| printf '%s' "$USER" | |
| return | |
| fi | |
| if id -un >/dev/null 2>&1; then | |
| id -un | |
| return | |
| fi | |
| printf '%s' 'claude-code-user' | |
| } | |
| global_config_path() { | |
| if [[ -f "$LEGACY_CONFIG_PATH" ]]; then | |
| printf '%s' "$LEGACY_CONFIG_PATH" | |
| return | |
| fi | |
| if [[ "$CLAUDE_CONFIG_DIR_SET" -eq 1 ]]; then | |
| printf '%s/.claude%s.json' "$RESOLVED_CONFIG_DIR" "$OAUTH_SUFFIX" | |
| return | |
| fi | |
| printf '%s/.claude%s.json' "$HOME" "$OAUTH_SUFFIX" | |
| } | |
| keychain_service_name() { | |
| local service_suffix="$1" | |
| local dir_hash="" | |
| if [[ "$CLAUDE_CONFIG_DIR_SET" -eq 1 ]]; then | |
| dir_hash="-$(hash_prefix "$RESOLVED_CONFIG_DIR")" | |
| fi | |
| printf 'Claude Code%s%s%s' "$OAUTH_SUFFIX" "$service_suffix" "$dir_hash" | |
| } | |
| keychain_item_exists() { | |
| local service_name="$1" | |
| is_macos || return 1 | |
| security find-generic-password -a "$USERNAME" -w -s "$service_name" >/dev/null 2>&1 | |
| } | |
| show_auth_state() { | |
| echo "Mode: show" | |
| echo "Config dir: $RESOLVED_CONFIG_DIR" | |
| if [[ "$CLAUDE_CONFIG_DIR_SET" -eq 1 ]]; then | |
| echo "CLAUDE_CONFIG_DIR semantics: explicit" | |
| else | |
| echo "CLAUDE_CONFIG_DIR semantics: default" | |
| fi | |
| echo "OAuth suffix: ${OAUTH_SUFFIX:-<prod>}" | |
| echo "Credentials file: $CREDENTIALS_PATH" | |
| if [[ -f "$CREDENTIALS_PATH" ]]; then | |
| echo "Credentials file exists: yes" | |
| else | |
| echo "Credentials file exists: no" | |
| fi | |
| echo "Global config file: $GLOBAL_CONFIG_PATH" | |
| if [[ -f "$GLOBAL_CONFIG_PATH" ]]; then | |
| echo "Global config exists: yes" | |
| else | |
| echo "Global config exists: no" | |
| fi | |
| if is_macos; then | |
| echo "OAuth keychain service: $OAUTH_KEYCHAIN_SERVICE" | |
| if keychain_item_exists "$OAUTH_KEYCHAIN_SERVICE"; then | |
| echo "OAuth keychain entry exists: yes" | |
| else | |
| echo "OAuth keychain entry exists: no" | |
| fi | |
| echo "Legacy API key keychain service: $API_KEY_KEYCHAIN_SERVICE" | |
| if keychain_item_exists "$API_KEY_KEYCHAIN_SERVICE"; then | |
| echo "Legacy API key keychain entry exists: yes" | |
| else | |
| echo "Legacy API key keychain entry exists: no" | |
| fi | |
| else | |
| echo "Keychain inspection: skipped (non-macOS)" | |
| fi | |
| if [[ "$CLAUDE_CONFIG_DIR_SET" -eq 1 ]]; then | |
| printf 'Run Claude with: CLAUDE_CONFIG_DIR=%q claude\n' "$RESOLVED_CONFIG_DIR" | |
| else | |
| echo "Run Claude with its default config dir." | |
| fi | |
| } | |
| sha256_short() { | |
| local path="$1" | |
| if command -v shasum >/dev/null 2>&1; then | |
| shasum -a 256 "$path" | awk '{print substr($1, 1, 16)}' | |
| return | |
| fi | |
| if command -v sha256sum >/dev/null 2>&1; then | |
| sha256sum "$path" | awk '{print substr($1, 1, 16)}' | |
| return | |
| fi | |
| python3 - <<'PY' "$path" | |
| import hashlib | |
| import pathlib | |
| import sys | |
| path = pathlib.Path(sys.argv[1]) | |
| print(hashlib.sha256(path.read_bytes()).hexdigest()[:16]) | |
| PY | |
| } | |
| file_stat_line() { | |
| local path="$1" | |
| local kind size mtime hash | |
| if [[ -L "$path" ]]; then | |
| kind="symlink" | |
| elif [[ -d "$path" ]]; then | |
| kind="dir" | |
| elif [[ -f "$path" ]]; then | |
| kind="file" | |
| else | |
| printf 'missing|%s\n' "$path" | |
| return | |
| fi | |
| if [[ "$kind" == "dir" ]]; then | |
| size="-" | |
| hash="-" | |
| elif [[ "$kind" == "symlink" ]]; then | |
| size="-" | |
| hash="->$(readlink "$path")" | |
| else | |
| size="$(wc -c <"$path" | tr -d ' ')" | |
| hash="$(sha256_short "$path")" | |
| fi | |
| if stat -f '%Sm' -t '%Y-%m-%dT%H:%M:%S%z' "$path" >/dev/null 2>&1; then | |
| mtime="$(stat -f '%Sm' -t '%Y-%m-%dT%H:%M:%S%z' "$path")" | |
| else | |
| mtime="$(stat -c '%y' "$path" 2>/dev/null | awk '{print $1"T"$2}')" | |
| fi | |
| printf '%s|%s|%s|%s|%s\n' "$kind" "$path" "$size" "$mtime" "$hash" | |
| } | |
| collect_path_snapshot() { | |
| local outfile="$1" | |
| shift | |
| : >"$outfile" | |
| for target in "$@"; do | |
| file_stat_line "$target" >>"$outfile" | |
| done | |
| } | |
| capture_state_bundle() { | |
| local phase_dir="$1" | |
| show_auth_state >"$phase_dir/auth-state-show.txt" | |
| collect_path_snapshot "$phase_dir/path-snapshot.txt" "${TRACKED_PATHS[@]}" | |
| { | |
| printf 'path|status\n' | |
| for target in "${TRACKED_PATHS[@]}"; do | |
| if [[ -e "$target" || -L "$target" ]]; then | |
| printf '%s|present\n' "$target" | |
| else | |
| printf '%s|missing\n' "$target" | |
| fi | |
| done | |
| } >"$phase_dir/presence.txt" | |
| } | |
| LABEL="" | |
| OUTPUT_PARENT="${TMPDIR:-/tmp}/nono-live-claude-auth-failure" | |
| CLAUDE_BIN="${CLAUDE_BIN:-$(command -v claude || true)}" | |
| NONO_BIN="${NONO_BIN:-$(command -v nono || true)}" | |
| CLAUDE_CONFIG_DIR_VALUE="" | |
| CLAUDE_CONFIG_DIR_SET=0 | |
| NOTES=() | |
| while [[ $# -gt 0 ]]; do | |
| case "$1" in | |
| --label) | |
| [[ $# -ge 2 ]] || fail "--label requires a value" | |
| LABEL="$2" | |
| shift 2 | |
| ;; | |
| --nono-bin) | |
| [[ $# -ge 2 ]] || fail "--nono-bin requires a value" | |
| NONO_BIN="$2" | |
| shift 2 | |
| ;; | |
| --claude-bin) | |
| [[ $# -ge 2 ]] || fail "--claude-bin requires a value" | |
| CLAUDE_BIN="$2" | |
| shift 2 | |
| ;; | |
| --output-dir) | |
| [[ $# -ge 2 ]] || fail "--output-dir requires a value" | |
| OUTPUT_PARENT="$2" | |
| shift 2 | |
| ;; | |
| --claude-config-dir) | |
| [[ $# -ge 2 ]] || fail "--claude-config-dir requires a value" | |
| CLAUDE_CONFIG_DIR_VALUE="$2" | |
| CLAUDE_CONFIG_DIR_SET=1 | |
| shift 2 | |
| ;; | |
| --note) | |
| [[ $# -ge 2 ]] || fail "--note requires a value" | |
| NOTES+=("$2") | |
| shift 2 | |
| ;; | |
| --help|-h) | |
| usage | |
| exit 0 | |
| ;; | |
| *) | |
| fail "unknown argument: $1" | |
| ;; | |
| esac | |
| done | |
| [[ -n "$CLAUDE_BIN" && -x "$CLAUDE_BIN" ]] || fail "claude binary not found or not executable" | |
| [[ -n "$NONO_BIN" && -x "$NONO_BIN" ]] || fail "nono binary not found or not executable" | |
| OS_NAME="$(uname -s)" | |
| ARCH_NAME="$(uname -m)" | |
| CLAUDE_REALPATH="$(detect_realpath "$CLAUDE_BIN")" | |
| NONO_REALPATH="$(detect_realpath "$NONO_BIN")" | |
| if [[ "$CLAUDE_CONFIG_DIR_SET" -eq 1 ]]; then | |
| CONFIG_MODE="explicit" | |
| RESOLVED_CONFIG_DIR="$CLAUDE_CONFIG_DIR_VALUE" | |
| else | |
| CONFIG_MODE="default" | |
| RESOLVED_CONFIG_DIR="$HOME/.claude" | |
| fi | |
| RESOLVED_LOCK_DIR="${RESOLVED_CONFIG_DIR}.lock" | |
| OAUTH_SUFFIX="$(oauth_file_suffix)" | |
| CREDENTIALS_PATH="$RESOLVED_CONFIG_DIR/.credentials.json" | |
| LEGACY_CONFIG_PATH="$RESOLVED_CONFIG_DIR/.config.json" | |
| GLOBAL_CONFIG_PATH="$(global_config_path)" | |
| USERNAME="$(get_username)" | |
| OAUTH_KEYCHAIN_SERVICE="$(keychain_service_name '-credentials')" | |
| API_KEY_KEYCHAIN_SERVICE="$(keychain_service_name '')" | |
| TRACKED_PATHS=( | |
| "$RESOLVED_CONFIG_DIR" | |
| "$RESOLVED_LOCK_DIR" | |
| "$CREDENTIALS_PATH" | |
| "$GLOBAL_CONFIG_PATH" | |
| "$HOME/.claude.json" | |
| "$HOME/.claude.json.lock" | |
| "$HOME/.cache/claude" | |
| "$HOME/.cache/claude-cli-nodejs" | |
| ) | |
| mkdir -p "$OUTPUT_PARENT" | |
| STAMP="$(date '+%Y%m%d-%H%M%S')" | |
| RUN_NAME="$STAMP" | |
| if [[ -n "$LABEL" ]]; then | |
| RUN_NAME="${RUN_NAME}-$(sanitize_label "$LABEL")" | |
| else | |
| RUN_NAME="${RUN_NAME}-live-auth-failure" | |
| fi | |
| RUN_DIR="$OUTPUT_PARENT/$RUN_NAME" | |
| mkdir -p "$RUN_DIR" | |
| RUN_LOG="$RUN_DIR/run.log" | |
| ENV_DIR="$RUN_DIR/env-capture" | |
| REPRO_DIR="$RUN_DIR/repro" | |
| BEFORE_DIR="$REPRO_DIR/before" | |
| AFTER_DIR="$REPRO_DIR/after" | |
| mkdir -p "$ENV_DIR" "$REPRO_DIR" "$BEFORE_DIR" "$AFTER_DIR" | |
| if ((${#NOTES[@]} > 0)); then | |
| write_header_file "$RUN_DIR/notes.txt" "${NOTES[@]}" | |
| else | |
| : >"$RUN_DIR/notes.txt" | |
| fi | |
| { | |
| echo "timestamp=$STAMP" | |
| echo "label=$LABEL" | |
| echo "os=$OS_NAME" | |
| echo "arch=$ARCH_NAME" | |
| echo "claude_bin=$CLAUDE_BIN" | |
| echo "claude_realpath=$CLAUDE_REALPATH" | |
| echo "nono_bin=$NONO_BIN" | |
| echo "nono_realpath=$NONO_REALPATH" | |
| echo "config_mode=$CONFIG_MODE" | |
| echo "claude_config_dir=$RESOLVED_CONFIG_DIR" | |
| echo "run_dir=$RUN_DIR" | |
| } >"$RUN_DIR/metadata.env" | |
| cat >"$RUN_DIR/README.md" <<EOF | |
| # Live Claude Auth Failure Capture | |
| - Timestamp: $STAMP | |
| - Label: ${LABEL:-<unset>} | |
| - OS: $OS_NAME | |
| - Arch: $ARCH_NAME | |
| - Claude binary: \`$CLAUDE_BIN\` | |
| - Claude real path: \`$CLAUDE_REALPATH\` | |
| - nono binary: \`$NONO_BIN\` | |
| - nono real path: \`$NONO_REALPATH\` | |
| - Config mode: $CONFIG_MODE | |
| - Claude config dir: \`$RESOLVED_CONFIG_DIR\` | |
| - Run directory: \`$RUN_DIR\` | |
| ## Files | |
| - \`metadata.env\` | |
| - \`notes.txt\` | |
| - \`run.log\` | |
| - \`env-capture/\` environment and auth metadata | |
| - \`repro/\` controlled in-sandbox repro attempt | |
| - \`NEXT_STEPS.txt\` | |
| EOF | |
| log "Capturing current environment state" | |
| run_and_capture "$ENV_DIR/system.txt" /bin/sh -c ' | |
| uname -a | |
| echo | |
| sw_vers 2>/dev/null || true | |
| echo | |
| system_profiler SPSoftwareDataType 2>/dev/null || true | |
| ' | |
| run_and_capture "$ENV_DIR/claude-version.txt" /bin/sh -c " | |
| printf 'command -v claude: %s\n' \"\$(command -v claude || true)\" | |
| printf 'claude bin argument: %s\n' \"$CLAUDE_BIN\" | |
| printf 'claude real path: %s\n' \"$CLAUDE_REALPATH\" | |
| echo | |
| \"$CLAUDE_BIN\" --version || true | |
| " | |
| run_and_capture "$ENV_DIR/nono-version.txt" /bin/sh -c " | |
| printf 'command -v nono: %s\n' \"\$(command -v nono || true)\" | |
| printf 'nono bin argument: %s\n' \"$NONO_BIN\" | |
| printf 'nono real path: %s\n' \"$NONO_REALPATH\" | |
| echo | |
| \"$NONO_BIN\" --version || true | |
| " | |
| run_and_capture "$ENV_DIR/claude-paths.txt" /bin/sh -c " | |
| which -a claude || true | |
| echo | |
| ls -l \"$CLAUDE_BIN\" || true | |
| echo | |
| ls -ld \"$(dirname "$CLAUDE_BIN")\" || true | |
| echo | |
| ls -ld \"$HOME/.local\" \"$HOME/.local/bin\" \"$HOME/.local/share\" \"$HOME/.local/share/claude\" 2>/dev/null || true | |
| echo | |
| ls -ld \"$HOME/.claude\" \"$HOME/.claude.lock\" \"$HOME/.claude.json\" \"$HOME/.claude.json.lock\" 2>/dev/null || true | |
| " | |
| run_and_capture "$ENV_DIR/package-manager-hints.txt" /bin/sh -c ' | |
| npm list -g --depth=0 2>/dev/null | grep -i claude || true | |
| echo | |
| brew list --versions 2>/dev/null | grep -i claude || true | |
| echo | |
| python3 -m pip list 2>/dev/null | grep -i claude || true | |
| ' | |
| run_and_capture "$ENV_DIR/env-vars.txt" /bin/sh -c ' | |
| env | sort | grep -E "^(CLAUDE|ANTHROPIC|NONO|CARGO|HOME|PATH|USER|SHELL|TMPDIR|XDG_)=" || true | |
| ' | |
| show_auth_state >"$ENV_DIR/auth-state-show.txt" | |
| startup_env=(env) | |
| if [[ "$CLAUDE_CONFIG_DIR_SET" -eq 1 ]]; then | |
| startup_env+=(CLAUDE_CONFIG_DIR="$RESOLVED_CONFIG_DIR") | |
| fi | |
| set +e | |
| run_interactive_and_capture \ | |
| "$ENV_DIR/startup-probe.txt" \ | |
| "${startup_env[@]}" \ | |
| "$NONO_BIN" run --profile claude-code --allow-cwd -- "$CLAUDE_BIN" | |
| startup_probe_rc=$? | |
| set -e | |
| printf '%s\n' "$startup_probe_rc" >"$ENV_DIR/startup-probe-exit-code.txt" | |
| if [[ "$startup_probe_rc" -ne 0 ]]; then | |
| log "Startup probe exited with status $startup_probe_rc; continuing with full capture" | |
| fi | |
| log "Capturing before snapshot" | |
| capture_state_bundle "$BEFORE_DIR" | |
| OPEN_PROBE_DIR="" | |
| OPEN_WRAPPER_LOG="" | |
| cleanup() { | |
| if [[ -n "$OPEN_PROBE_DIR" && -d "$OPEN_PROBE_DIR" ]]; then | |
| rm -rf "$OPEN_PROBE_DIR" | |
| fi | |
| } | |
| trap cleanup EXIT | |
| if is_macos; then | |
| OPEN_PROBE_DIR="$(mktemp -d "${TMPDIR:-/tmp}/nono-claude-open-probe.XXXXXX")" | |
| OPEN_WRAPPER_LOG="$REPRO_DIR/open-wrapper.log" | |
| : >"$OPEN_WRAPPER_LOG" | |
| chmod 600 "$OPEN_WRAPPER_LOG" | |
| log "Installing macOS PATH open probe wrapper" | |
| cat >"$OPEN_PROBE_DIR/open" <<'EOF' | |
| #!/bin/sh | |
| log_file="${NONO_OPEN_WRAPPER_LOG:?}" | |
| { | |
| printf 'pid=%s argc=%s\n' "$$" "$#" | |
| i=1 | |
| for arg in "$@"; do | |
| printf 'arg[%s]=%s\n' "$i" "$arg" | |
| i=$((i + 1)) | |
| done | |
| printf '\n' | |
| } >>"$log_file" | |
| exec /usr/bin/open "$@" | |
| EOF | |
| chmod 755 "$OPEN_PROBE_DIR/open" | |
| fi | |
| nono_args=(run --profile claude-code --allow-cwd) | |
| if [[ "$CLAUDE_CONFIG_DIR_SET" -eq 1 ]]; then | |
| nono_args+=(--allow "$RESOLVED_CONFIG_DIR" --allow "$RESOLVED_LOCK_DIR") | |
| fi | |
| nono_args+=(--) | |
| launch_env=(env) | |
| if [[ "$CLAUDE_CONFIG_DIR_SET" -eq 1 ]]; then | |
| launch_env+=(CLAUDE_CONFIG_DIR="$RESOLVED_CONFIG_DIR") | |
| fi | |
| if [[ -n "$OPEN_PROBE_DIR" ]]; then | |
| launch_env+=(PATH="$OPEN_PROBE_DIR:$PATH") | |
| launch_env+=(NONO_OPEN_WRAPPER_LOG="$OPEN_WRAPPER_LOG") | |
| fi | |
| log "Launching Claude under nono" | |
| log "Report dir: $REPRO_DIR" | |
| log "Inside Claude, reproduce the auth failure once, then exit." | |
| set +e | |
| run_interactive_and_capture \ | |
| "$REPRO_DIR/claude-session.log" \ | |
| "${launch_env[@]}" \ | |
| "$NONO_BIN" "${nono_args[@]}" "$CLAUDE_BIN" | |
| launch_rc=$? | |
| set -e | |
| printf '%s\n' "$launch_rc" >"$REPRO_DIR/launch-exit-code.txt" | |
| log "Claude session exited with status $launch_rc" | |
| log "Capturing after snapshot" | |
| capture_state_bundle "$AFTER_DIR" | |
| if [[ -f "$OPEN_WRAPPER_LOG" ]]; then | |
| if [[ -s "$OPEN_WRAPPER_LOG" ]]; then | |
| log "Open probe captured PATH-resolved open invocations" | |
| printf 'hit\n' >"$REPRO_DIR/open-probe-status.txt" | |
| else | |
| log "Open probe did not capture any PATH-resolved open invocations" | |
| printf 'no-hit\n' >"$REPRO_DIR/open-probe-status.txt" | |
| fi | |
| fi | |
| if diff -u "$BEFORE_DIR/path-snapshot.txt" "$AFTER_DIR/path-snapshot.txt" \ | |
| >"$REPRO_DIR/path-snapshot.diff"; then | |
| log "Tracked file snapshot unchanged" | |
| else | |
| log "Tracked file snapshot changed; see path-snapshot.diff" | |
| fi | |
| login_observed="no" | |
| if grep -Eq "Login successful|Not logged in|Run /login|Select login method" \ | |
| "$REPRO_DIR/claude-session.log" 2>/dev/null; then | |
| login_observed="yes" | |
| fi | |
| open_probe_status="not-run" | |
| if [[ -f "$REPRO_DIR/open-probe-status.txt" ]]; then | |
| open_probe_status="$(tr -d '\n' <"$REPRO_DIR/open-probe-status.txt")" | |
| fi | |
| path_snapshot_changed="no" | |
| if [[ -s "$REPRO_DIR/path-snapshot.diff" ]]; then | |
| path_snapshot_changed="yes" | |
| fi | |
| cat >"$REPRO_DIR/summary.env" <<EOF | |
| launch_mode=profile | |
| launch_exit_code=$launch_rc | |
| login_observed=$login_observed | |
| open_probe_status=$open_probe_status | |
| path_snapshot_changed=$path_snapshot_changed | |
| report_dir=$REPRO_DIR | |
| EOF | |
| cat >"$REPRO_DIR/README.md" <<EOF | |
| # Claude Auth Repro Run | |
| - Timestamp: $STAMP | |
| - OS: $OS_NAME | |
| - Launch mode: profile | |
| - Claude binary: $CLAUDE_BIN | |
| - nono binary: $NONO_BIN | |
| - Claude config dir: $RESOLVED_CONFIG_DIR | |
| - Report directory: $REPRO_DIR | |
| - Claude exit code: $launch_rc | |
| ## Files | |
| - \`claude-session.log\` full nono + Claude terminal log | |
| - \`before/\` auth-state and file snapshots before launch | |
| - \`after/\` auth-state and file snapshots after launch | |
| - \`path-snapshot.diff\` diff of tracked file metadata before vs after | |
| - \`open-wrapper.log\` macOS PATH-open probe output, when enabled | |
| - \`summary.env\` compact outcome summary | |
| EOF | |
| ARCHIVE_PATH="${RUN_DIR}.tar.gz" | |
| tar -C "$(dirname "$RUN_DIR")" -czf "$ARCHIVE_PATH" "$(basename "$RUN_DIR")" | |
| cat >"$RUN_DIR/NEXT_STEPS.txt" <<EOF | |
| Capture complete. | |
| Primary artifacts: | |
| - Directory: $RUN_DIR | |
| - Archive: $ARCHIVE_PATH | |
| Send the archive path or the archive itself for analysis. | |
| Do not manually retype file contents from the bundle. | |
| EOF | |
| cat <<EOF | |
| Live auth failure capture complete. | |
| Run directory: $RUN_DIR | |
| Archive: $ARCHIVE_PATH | |
| EOF |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment