Investigate a site's auth-tool compile
SkillMonitoring & opsInvestigate 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.
No other account needed.
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.jsonis hook events only (SessionStart / init / success). The agent's actual tool calls + reasoning are in a separate claude-cli transcript keyed by thesession_idinside 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
| Signal | Meaning |
|---|---|
.compile-log.json mtime advancing, no .compile-done.json | Ongoing — 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 phone | Ongoing, initiate fired — approve it |
.compile-done.json present + workflow.json/index.ts emitted | Completed (success) — emit() only runs on success |
run log says Auth tool compiled + session stored / compilation failed | Completed — 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
| Path | Contents |
|---|---|
~/.imprint/<site>/authenticate_<site>/.compile-log.json | Hook events only (has session_id) |
~/.claude/projects/**/<session_id>.jsonl | The agent transcript — every tool call + result |
~/.imprint/<site>/authenticate_<site>/.compile-done.json | Final result / outcome |
~/.imprint/<site>/authenticate_<site>/workflow.json | The emitted auth tool (captures, authConfig) |
~/.imprint/<site>/authenticate_<site>/.tool-plan.md | Plan injected into the agent's first message |
| the teach run's stdout/stderr | Spinner-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.json → run_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
| Symptom | Likely cause / fix |
|---|---|
FORBIDDEN/BAD_RESPONSE + "Access Denied" on the credential POST | login-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 attempts | poll body/terminal wrong, or you didn't approve in time; check pollBody/pollTerminal, raise IMPRINT_AUTH_POLL_ATTEMPTS |
STATE_MISSING on submit_otp/complete | a ${state.X} not echoed via twoFactorContext or captured on an initiate request; check captures (predicate path?) |
BUDGET_EXHAUSTED / ATTEMPT_BUDGET_EXHAUSTED | too many initiates; a corrected workflow needs a fresh run |
no credentials … skipping auth compile | run imprint credential set <site> (username/password) |
agent called give_up | login can't be reproduced (creds rejected, unsolvable CAPTCHA, enrollment page) — read its message |
codex-cli cannot compile an authenticate tool | auth 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
| Variable | Effect |
|---|---|
IMPRINT_DEBUG=1 | Verbose stderr (HTTP, cookies, Chromium, stack traces) |
IMPRINT_COMPILE_ACT_SPACING_MS=0 | Skip the 25s compile-time replay pacing |
IMPRINT_CDP_HEADED=1 | Force headed cdp-replay for any rung (auth is headed already) |
IMPRINT_AUTH_MAX_INITIATE / _ATTEMPTS | Challenge cap (2) / attempt cap (5) for live initiate |
IMPRINT_AUTH_POLL_ATTEMPTS | Bound 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