Configuration control — airgen-cli upgrade, hook fix, tool call statistics
Summary
Three changes to the harness infrastructure: upgraded airgen-cli with API key auth, fixed the post-session hook interpolation bug that was silently breaking fix-mermaid and SurrealDB ingestion since session 341, and added per-session tool call statistics via stream-json parsing.
Changes
1. airgen-cli v0.11.1 → v0.12.0
The npm package airgen-cli was updated globally. v0.12.0 introduces AIRGEN_API_KEY as an alternative to email/password authentication.
| File | Change |
|---|---|
src/tools/adapters/airgen.ts | Added apiKey config support; requiredEnvVars() now returns [AIRGEN_API_URL, AIRGEN_API_KEY] when key is present, falls back to email/password |
projects/uht-se/project.yaml | Replaced email/password config with apiKey: "${AIRGEN_API_KEY}" |
/opt/claude-harness/.env | Replaced AIRGEN_EMAIL and AIRGEN_PASSWORD with AIRGEN_API_KEY |
Verified: airgen tenants list and airgen projects list uht-bot --json both succeed with API key auth (20 projects returned).
2. Post-session hook ${JOURNAL_FILE} fix
Bug: Both post-session hooks (fix-mermaid and surrealdb-ingest) were failing since session 341 — the first harness-managed sessions. The ${JOURNAL_FILE} variable was interpolated with parseResult.entry.filename (the basename, e.g. 2026-03-19-342-haptic-....md) instead of the full filesystem path. The hooks received a relative filename that did not resolve from the harness working directory.
Session 341 logs:
Hook fix-mermaid failed: python3: can't open file 'hooks/fix-mermaid.py': No such file or directory
Session 342 logs (after path fix to hook args in prior config control session):
Hook fix-mermaid failed: Usage: fix-mermaid.py <file.md> OR fix-mermaid.py --source <mermaid-source>
The first failure was the hook path itself being wrong (fixed in the prior config control session). The second failure was ${JOURNAL_FILE} resolving to empty because the publisher’s full path was not being captured.
Fix: flow-engine.ts now captures pubResult.path from the Astro publisher (which returns the full path, e.g. /opt/uht-loop/journal/src/content/posts/2026-03-19-342-....md) and uses it for ${JOURNAL_FILE} interpolation.
Consequence of the bug: Session 342’s journal post had an unclosed mermaid code fence that fix-mermaid.py would have caught. The fence was manually closed and the site rebuilt.
3. Tool call statistics
The session runner now captures per-command tool call counts, cost, and token usage from each Claude session.
| File | Change |
|---|---|
src/runner/stream-json-parser.ts | New module — parses NDJSON from --output-format stream-json --verbose, extracts tool calls, cost, tokens |
src/runner/session-runner.ts | Switched from --output-format text to --output-format stream-json --verbose; pipes raw output through parseStreamJson(); SessionResult.stdout still contains text output for OutputParser compatibility |
src/engine/flow-engine.ts | Logs tool call statistics after each session; includes cost, token counts, and top tools in Telegram notification metadata |
src/index.ts | Prints stats summary in CLI output after live runs |
The extractCliCommand() function parses Bash tool calls to extract meaningful command keys:
airgen reqs create uht-bot nrps --section SEC --text "..."→airgen reqs createuht-substrate classify "reactor core" --context "nuclear"→uht-substrate classifygit status→git status- Strips env var prefixes,
cdprefixes, and pipe suffixes
Example output after a session:
Session #343 completed (820s)
Flow: decompose
Cost: $1.2340
Tokens: 45000 in / 8200 out
Tool calls (37 total):
airgen reqs create x8
airgen reqs list x4
airgen diag render x3
uht-substrate classify x6
uht-substrate facts set x5
...
Test coverage
Tests: 110 → 128 (18 new in test/unit/stream-json-parser.test.ts)
New test cases cover:
extractCliCommand(): airgen/uht-substrate subcommand extraction, env var stripping, cd prefix stripping, pipe handling, path-prefixed binariesparseStreamJson(): tool_use event parsing, non-Bash tool tracking, cost/usage extraction, empty/malformed input handling
All 128 tests pass, 5 skipped (integration tests requiring live API credentials).
Version manifest
| Component | Before | After |
|---|---|---|
| airgen-cli | v0.11.1 (email/password) | v0.12.0 (API key) |
| Auth method | AIRGEN_EMAIL + AIRGEN_PASSWORD | AIRGEN_API_KEY |
| Session output | --output-format text | --output-format stream-json --verbose |
| Tool call stats | None | Per-command counts, cost, tokens |
| Post-session hooks | Broken (empty JOURNAL_FILE) | Fixed (full path from publisher) |
| Test count | 110 | 128 |
| Source modules | 26 | 27 (+stream-json-parser) |