Investigate a site's auth-tool compile

SkillMonitoring & ops

Investigate a site's auth-tool compile — ongoing or completed. Locate the real agent transcript (not just the hook log), read the live 2FA verification, inspect the emitted workflow.json, confirm the login actually completed, and diagnose "no auth tool compiled".

Available today. Use it from your connected AI after setup.

Connect ahel once, and every AI you use reads what you have installed.

Then ask your AI: use the Investigate a site's auth-tool compile skill

What this skill tells your AI

The instructions your AI receives, as published by ashaychangwani/imprint in .claude/skills/imprint-auth-compile-log/SKILL.md and read by ahel’s review.

The authenticate_<site> tool is compiled by a separate agent loop from data tools (auth-compile-agent.ts → an MCP-server-driven claude-cli/codex session; the live login runs in auth-verifier.ts on headed cdp-replay). Its logs live in a few places — and the most important one is not the file you'd guess.

Key gotcha: ~/.imprint/<site>/authenticate_<site>/.compile-log.json is hook events only (SessionStart / init / success). The agent's actual tool calls + reasoning are in a separate claude-cli transcript keyed by the session_id inside that hook log. Step 2 shows how to find it.

Substitute the real site slug for <site> throughout (e.g. amex-fhr, remitly). The auth tool dir is always ~/.imprint/<site>/authenticate_<site>/.

Step 1 — Ongoing or completed?

SITE=<site>; AUTHDIR=~/.imprint/$SITE/authenticate_$SITE

# Is a teach process still alive? (auth runs HEADED — a visible Chrome window is open mid-run)
pgrep -fl "cli.ts teach $SITE" ; pgrep -afl "Chrome for Testing|Chromium" | head -1

# Completed? these appear only after the auth block finishes
ls -la "$AUTHDIR"/.compile-done.json "$AUTHDIR"/workflow.json "$AUTHDIR"/index.ts 2>/dev/null
SignalMeaning
.compile-log.json mtime advancing, no .compile-done.jsonOngoing — agent still shaping/verifying
spinner shows Auth compile: turn N — verify initiate …Ongoing, in live verification
a headed Chrome window is open + a push/OTP hit your phoneOngoing, initiate fired — approve it
.compile-done.json present + workflow.json/index.ts emittedCompleted (success)emit() only runs on success
run log says Auth tool compiled + session stored / compilation failedCompleted — success / failure

Step 2 — Find the artifacts (incl. the real transcript)

SITE=<site>; AUTHDIR=~/.imprint/$SITE/authenticate_$SITE

# 1. Hook log — grab the claude-cli session_id from it
SID=$(python3 -c "import json,sys; d=json.load(open('$AUTHDIR/.compile-log.json')); print(next((e.get('session_id') for e in d if e.get('session_id')), ''))")
echo "auth session: $SID"

# 2. The REAL agent transcript (full tool-call conversation), located by session_id
T=$(find ~/.claude/projects -name "$SID.jsonl" 2>/dev/null | head -1); echo "transcript: $T"

# 3. Final outcome
cat "$AUTHDIR/.compile-done.json" 2>/dev/null
PathContents
~/.imprint/<site>/authenticate_<site>/.compile-log.jsonHook events only (has session_id)
~/.claude/projects/**/<session_id>.jsonlThe agent transcript — every tool call + result
~/.imprint/<site>/authenticate_<site>/.compile-done.jsonFinal result / outcome
~/.imprint/<site>/authenticate_<site>/workflow.jsonThe emitted auth tool (captures, authConfig)
~/.imprint/<site>/authenticate_<site>/.tool-plan.mdPlan injected into the agent's first message
the teach run's stdout/stderrSpinner-only during auth (logs are muted); see Step 4

Step 3 — Read what the agent actually did

The single most useful view — the ordered tool calls (did it read the recording, write the workflow, reach 2FA, complete?):

python3 - "$T" <<'PY'
import json,sys
n=0
for line in open(sys.argv[1]):
    line=line.strip()
    if not line: continue
    try: o=json.loads(line)
    except: continue
    c=(o.get('message',o) or {}).get('content')
    if not isinstance(c,list): continue
    for b in c:
        if isinstance(b,dict) and b.get('type')=='tool_use':
            n+=1
            name=b.get('name','').replace('mcp__imprint-compile__','')
            print(f"{n:3} {name:22} {json.dumps(b.get('input',{}))[:150]}")
PY

A healthy run looks like: read_session_summary → several read_request/read_response_body → one write_file workflow.jsonrun_verification {phase:initiate} → (prompt_user) → run_verification {phase:complete}done. Read the agent's text/give_up message for why if it bailed.

Step 4 — Read the live 2FA verification

Auth logs are muted under the spinner, so read them from the run's captured output (if you teed it to a file) or from the task output file. Strip the ANSI/spinner noise:

LOG=/tmp/<your-teach-run>.log   # wherever you redirected the teach run
sed -E 's/\x1b\[[0-9;]*m//g; s/\x1b\[[0-9]*[A-Z]//g; s/\x1b\[J//g' "$LOG" | tr '\r' '\n' \
  | grep -oE "verify (initiate|complete|submit_otp) [A-Z_]+[^◐◓◑◒]*|AWAITING_2FA|Auth tool compiled[^◐◓◑◒]*|compilation failed|session stored|attempt [0-9]+/[0-9]+|BUDGET_EXHAUSTED|ATTEMPT_BUDGET_EXHAUSTED" \
  | sort -u | tail -20

AWAITING_2FA on initiate is the healthy outcome (the challenge was delivered) — it is labelled "FAILED" by the generic progress formatter but means "reached 2FA". Two budgets bound it: challenge cap (IMPRINT_AUTH_MAX_INITIATE, default 2) and attempt cap (IMPRINT_AUTH_MAX_INITIATE_ATTEMPTS, default 5).

Step 5 — Inspect the emitted workflow.json

python3 - "$AUTHDIR/workflow.json" <<'PY'
import json,sys
d=json.load(open(sys.argv[1]))
for i,r in enumerate(d.get('requests',[])):
    caps=[c.get('path') for c in r.get('captures',[])]
    print(f"[{i}] {r.get('method')} ...{r.get('url','')[-46:]} optional={r.get('optional',False)} caps={caps}")
ac=d.get('authConfig',{})
print("twoFactorType:",ac.get('twoFactorType'),"| initiateRequestCount:",ac.get('initiateRequestCount'))
print("pollEndpoint:",(ac.get('pollEndpoint') or '')[:70]," pollTerminal:",ac.get('pollTerminal'))
print("sessionCapture:",ac.get('sessionCapture'))
print("bootstrap.url:",(d.get('bootstrap') or {}).get('url'))
PY

What a robust tool has: bootstrap.url = the credential-entry page; predicate capture paths (challenges[type=push].token, not a fixed [0]) for variable-order challenge arrays; optional:true on best-effort steps (e.g. trust-device); for push, pollEndpoint+pollBody+pollTerminal; a sessionCapture only if data tools need a non-cookie bearer/CSRF token (omit for cookie-auth).

Step 6 — Did the login actually complete?

emit() (so workflow.json/index.ts) only runs on the agent calling done, but confirm a real session landed — not just pre-existing cookies:

SITE=<site>
ls -la ~/.imprint/$SITE/.cdp-jar.json   # mtime ≈ run time → the headed browser ran live
bun run src/cli.ts credential list $SITE | grep -i cookies   # count grows after a fresh login

Cross-check: the run says session stored (vs no live session stored), and the Step-3 transcript ends in done (not give_up). For push, the login can only complete if you approved it.

Step 7 — Independence / "did the agent cheat?" audit

To confirm the agent derived the tool from the recording (not by copying a hand-tuned answer):

# It must NOT read the answer key. Expect ZERO read_file/run_bash on workflow.json / *.bak / handoff:
grep -oiE "read_file|run_bash|groundtruth|workflow\.json\.bak|AMEX_AUTH_HANDOFF" "$T" | sort | uniq -c

Then read the first user message in the transcript — the build-plan authTool.notes legitimately describe the flow (that's the pipeline), but the predicate-path syntax and optional flag should be the agent's own application of the prompt, not present verbatim in the notes.

Failure signatures

SymptomLikely cause / fix
FORBIDDEN/BAD_RESPONSE + "Access Denied" on the credential POSTlogin-page sensor never ran → fix/add bootstrap.url; ensure auth ran headed cdp-replay (it should — runCdpReplay headed for toolKind==='authenticate')
AWAITING_2FA then Push not approved after N attemptspoll body/terminal wrong, or you didn't approve in time; check pollBody/pollTerminal, raise IMPRINT_AUTH_POLL_ATTEMPTS
STATE_MISSING on submit_otp/completea ${state.X} not echoed via twoFactorContext or captured on an initiate request; check captures (predicate path?)
BUDGET_EXHAUSTED / ATTEMPT_BUDGET_EXHAUSTEDtoo many initiates; a corrected workflow needs a fresh run
no credentials … skipping auth compilerun imprint credential set <site> (username/password)
agent called give_uplogin can't be reproduced (creds rejected, unsolvable CAPTCHA, enrollment page) — read its message
codex-cli cannot compile an authenticate toolauth verification is checkpoint-based; use claude-cli or anthropic-api

"No auth tool compiled at all"

If the recording had a login but no authenticate_<site> dir / no auth block ran, the planner (the only producer of buildPlan.authTool) was skipped. Check:

SITE=<site>
ls ~/.imprint/$SITE/.build-plan.json 2>/dev/null && \
  python3 -c "import json;d=json.load(open('$HOME/.imprint/$SITE/.build-plan.json'));print('authTool:',(d.get('authTool') or {}).get('toolName'))"
grep -oE '"(twoFactorDetected|twoFactorType)":[^,}]*|"loginRequestSeqs":\[[^]]*\]' ~/.imprint/$SITE/.teach-state.json | head

The planner runs when ≥2 tools are selected OR any login was detected (sharedContextHasAuth: loginRequestSeqs/credentialNames non-empty, or twoFactorDetected). If a login is detected but there's no .build-plan.json, re-run from the planner without redoing earlier stages:

bun run src/cli.ts teach <site> --from-step plan-prereqs   # interactive, so OTP/push prompts work

Useful env knobs

VariableEffect
IMPRINT_DEBUG=1Verbose stderr (HTTP, cookies, Chromium, stack traces)
IMPRINT_COMPILE_ACT_SPACING_MS=0Skip the 25s compile-time replay pacing
IMPRINT_CDP_HEADED=1Force headed cdp-replay for any rung (auth is headed already)
IMPRINT_AUTH_MAX_INITIATE / _ATTEMPTSChallenge cap (2) / attempt cap (5) for live initiate
IMPRINT_AUTH_POLL_ATTEMPTSBound an unattended push poll

Signals

GitHub stars
21
Forks
1
Last commit
Sep 2026
Advanced
Catalog kind
skill
Gateway key
imprint-auth-compile-log
Source
github.com/ashaychangwani/imprint