{"slug": "show-hn-claude-code-hooks-that-log-every-tool-call-124ms-per-call", "title": "Show HN: Claude Code hooks that log every tool call, 124ms per call", "summary": "A new open-source tool called homestead-memory logs every Claude Code tool call in a hash-chained, tamper-evident local ledger at a median cost of about 124ms per call (138ms p95, measured on an M3 Pro with a 4KB tool response), with the hook running as a fresh process twice per call — roughly 61ms before and 63ms after — since version 0.4.0. The package, installable via pip for Python 3.10+ on macOS, Linux, and Windows, records both the decision before a tool runs and the outcome after, and can export the ledger in the IETF draft-sharif-agent-audit-trail-01 format, which maps to EU AI Act Article 12. The author warns that the AAT draft is an unadopted individual Internet-Draft with IPR disclosure 7558 declaring RAND licensing with a possible royalty or fee, so the --format aat export should be treated as a convenience rather than a compliance foundation, while the native Ed25519 EvidencePack carries no such disclosure.", "body_md": "**Stop renting your mind.**\n\n**A tamper-evident record of what your AI agent actually did**, in a local file, that\nsomeone who does not trust you can verify.\nTamper-**evident**, not tamper-proof:\n[what it defends against and what it does not](#what-this-defends-against-and-what-it-does-not).\n\nAgents fail quietly. The run reports success, the tool returns 200, and the thing you\nasked for did not happen. There is usually no record to contradict it. This keeps one,\nand every entry is hash-chained to the one before it, so *see it catch a forged record,\nlive:*\n\n```\npip install homestead-memory      # Python 3.10+, macOS / Linux / Windows\n\nhsm watch --demo\n#      0  14:02:11  Bash    npm test\n#      1  14:02:19  Read    src/api/billing.py\n#      2  14:02:24  Edit    src/api/billing.py\n#\n# ② now someone edits record 1…\n#   !! chain break at index 1: hash_mismatch\n#      record content does not match its own hash (edited in place)\n#   exit 1  ·  gate it in CI like a test\n```\n\n**Got `No matching distribution found`?** macOS still ships Python 3.9 as its built-in\n`python3`, and this needs 3.10+. Nothing is wrong with the package. Either use a newer\nPython, or skip installing entirely:\n`uvx --from homestead-memory hsm watch --demo`\n([uv](https://docs.astral.sh/uv/) fetches a suitable Python for you.)\n\n```\nhsm hook --install     # prints a Claude Code hook; you paste it, nothing is edited for you\nhsm watch              # what your agent did, in order\n```\n\nEvery entry is hash-chained to the one before it, so editing, deleting, or reordering\nany record breaks every hash after it. `hsm watch` reports the break at the exact index\nand exits non-zero. Sign it and a wholly rebuilt chain is caught too.\n\n**Both phases are recorded, which is what makes it evidence rather than a log.** The hook\ncaptures the decision before a tool runs and the outcome after. A record of outcomes alone\nshows what happened; it cannot show that anything was authorised first. As\n[the IETF Agent Audit Trail draft](https://datatracker.ietf.org/doc/draft-sharif-agent-audit-trail/)\nputs it, \"a denial that is only logged after execution provides no evidence that the\ndenial was enforced\".\n\n```\nhsm export --format aat     # the same ledger as draft-sharif-agent-audit-trail-01\n```\n\nThat draft maps explicitly to EU AI Act Article 12, and this emits it **alongside** the\nnative format rather than replacing it, so nothing already on disk changes. Where we\ndiffer we say so: it specifies ECDSA P-256, which the AAT export uses, while the native\nEvidencePack stays Ed25519 so its verifier can remain standard-library only and a\nrecipient needs nothing installed.\n\n**Know this before you rely on the AAT export.** That draft is an individual\nInternet-Draft, not an adopted standard: it has no working group behind it and carries the\nusual notice that it \"is not endorsed by the IETF\". Its author has also filed\n[IPR disclosure 7558](https://datatracker.ietf.org/ipr/7558/), covering revisions 00 and 01\nin full, declaring \"Reasonable and Non-Discriminatory License to All Implementers with\nPossible Royalty/Fee\". RAND with a possible fee is not royalty-free, and this package is\nMIT, which grants you no patent licence. We ship the export because interoperability is\nworth having and we have asked the author to clarify what implementers are taking on.\nUntil there is an answer, treat `--format aat` as a convenience rather than something to\nbuild a compliance programme on. The native ledger and EvidencePack carry no such\ndisclosure, and they are what the rest of this README argues for.\n\n**What it costs you: about 124ms per tool call** (median; 138ms p95, measured on an M3 Pro\nwith a 4KB tool response). Since 0.4.0 the hook records both phases, so it runs as a fresh\nprocess **twice** per call, once before your tool runs and once after: about 61ms and 63ms\nrespectively. On a 100-call session that is roughly 12 seconds spread across the run.\nAlmost all of it is Python interpreter and import startup rather than the recording itself.\nIf that is too much for your loop, do not install the hook: the number is here so you can\ndecide before you find out.\n\nEarlier releases documented 68ms, which was correct when only `PostToolUse` was recorded.\nRecording the decision phase is what makes the ledger evidence of enforcement rather than\nof observation, and it costs a second process. Stating the higher number rather than the\none that flatters us is the same reason the rest of these figures are here.\n\n**This is a file, not a platform.** Agent observability tools are far richer than this\nand they want a deployment: the self-hosted ones document a production floor of several\nservices and roughly 16 GB of RAM. This is `pip install`, one hook line, and a JSONL\nfile on your disk. Different job. If you need dashboards, evals, and span analytics,\nuse one of those. If you want a record you can grep and prove, use this.\n\nSecret-shaped values are redacted and payloads truncated to a 200-character head, with a SHA-256 of the full original kept so the evidence survives redaction. That is a mitigation, not a guarantee: no pattern list is complete.\n\n```\nhsm export --evidence  # a pack anyone can verify with no install at all\n```\n\nThe pack carries the records, the signature, the public key, an integrity report, and a standard-library verifier a third party can read in full and run. It states what it does NOT prove, including that a signature only establishes origin if you already know which key to expect.\n\nTamper-**evident**, not tamper-proof. The difference matters, so here it is plainly.\n\n**It catches:**\n\n- \nA record edited in place. The hash stops matching and `hsm watch` names the index.\n- \nA record deleted or reordered. Every hash after it breaks.\n- \nA silently dropped write. Drops are recorded and reported, never swallowed.\n- \nA whole chain rebuilt from scratch by someone who recomputed every hash, **provided you\nran `hsm checkpoint`, you pin the expected signer, and they do not hold your key** . All\nthree matter. A checkpoint verified without a pinned key is self-asserted: it checks the\nsignature against whichever key sits beside it, so a rebuilt chain re-signed with the\nattacker's own key also passes.`hsm verify --deep` now says so when no key is pinned.\n\n```\nhsm verify --deep --signer <your pubkey>   # or the checkpoint is self-asserted\n```\n\n- \nAnything altered after you handed someone an EvidencePack, which they can check with no install and nothing from us.\n\n**A checkpoint covers its prefix, not the future.** Records appended after your last\ncheckpoint can be replaced with a correctly re-chained forgery and still verify, because a\nsignature cannot cover records that did not exist when it was made. `hsm verify` tells you\nhow many records are uncovered. Re-checkpoint often, or that tail grows.\n\n**It does not catch:**\n\n- **An attacker who holds your signing key.** The key lives at`~/.config/homestead-memory/ed25519_key` , on the same machine as the ledger. Anyone who\ncan rewrite the file can usually read that key, re-sign the rewrite, and pass. If that is\nyour threat model, run`hsm checkpoint --export` , publish the line somewhere they do not\ncontrol, and check the ledger against it later with`hsm checkpoint --verify` . A head\nhash reveals nothing about the records, so publishing one leaks nothing.\n- **A tool that lies.** See below.\n- Anything that never reached the ledger at all, such as a hook you did not install.\n\n```\nhsm checkpoint            # sign the current head\nhsm checkpoint --export   # one line to publish where an attacker cannot reach\nhsm checkpoint --verify   # check this ledger against a line you published earlier\n```\n\n`hsm watch` reports how much of the ledger the checkpoint covers, and fails if the\ncheckpoint no longer verifies. Everything appended since your last checkpoint is uncovered,\nso re-checkpoint often.\n\nThe hook receives what the harness says a tool did. It is not independent observation.\n\nIf a tool reports success while doing nothing, that is precisely the failure this exists to\ncatch, because you get a durable record of the claim and can compare it against reality.\nBut if a tool reports something false about *what* it did, the ledger will faithfully\nrecord a hash-chained, signed falsehood.\n\nThe record proves the record was not altered. It does not make the harness truthful.\n\nReasonable questions, and mostly they are different jobs.\n\n- **OpenTelemetry** gives you far richer tracing and an ecosystem this does not have. It is\nnot built to be tamper-evident, and spans usually leave the machine. Use both if you want:\nthey answer \"what happened, in detail\" and this answers \"can I prove it was not edited\".\n- **Signing your log files** gets you most of the way, and if you already do it you may not\nneed this. The differences are per-record chaining, so a single altered line is located\nrather than the whole file being invalidated, and a pack whose verifier a recipient runs\nwith nothing installed.\n- **Langfuse, Braintrust, Phoenix** are observability platforms with dashboards, evals and\nspan analytics this will never have. They also want a deployment. The self-hosted ones\ndocument a production floor of several services and roughly 16 GB of RAM. This is one hook\nline and a JSONL file. If you want dashboards, use them.\n\nThe narrow thing this does that those do not: produce a record a third party can verify without trusting you, without installing anything, and without the data leaving your machine.\n\nThe same tool owns the memory your agent reads and writes: plain markdown you can read,\n`git diff`, and walk away with. It **catches its own rot, tampering, and poisoning** with\nmechanical checks rather than an LLM judge, scores it 0 to 100, and exits non-zero so it\ngates CI and cron like a test suite.\n\n```\nnpm install -g @tobilu/qmd@2.1.0  # optional hybrid retrieval runtime\n\nhsm verify --demo\n# ① a clean vault           ✅  MEMORY INTACT — 100/100\n# ② rot is planted…         🔴  ROT DETECTED —   0/100\n#    🔴 [self_contradiction] the note argues with itself about its own status\n#    🔴 [uncited_claim]      a distilled claim has no source citation\n#    🔴 [dangling_citation]  a cited source no longer exists\n#    ⚠️  [broken_link]        a reference points at a note that isn't there\n```\n\nThat scoring is [RotBench](/fuckbigtech-ai/homestead-memory/blob/master/benchmarks/ROTBENCH.md), published as an open spec so the\nnumber is reproducible rather than self-reported.\n\n**Capture is Claude Code only right now.** The MCP integration below works anywhere MCP\ndoes; the hooks that record *every* tool call use Claude Code's `PreToolUse` and\n`PostToolUse`. Cursor and Codex need their own mechanisms and those are not built yet.\n\n**Codex is supported by import, not by hook**, since 0.4.1:\n\n```\nhsm import-session --dry-run     # newest Codex session, writes nothing\nhsm import-session -n 5          # the five most recent\n```\n\nCodex's tool hooks did not fire under `codex exec` when measured, but its rollout files\nrecord every call with input, output and a pairing id. Imported rows carry\n`meta.source=codex-rollout` and `hsm watch` prints **(imported)** beside them, because a\nrow read from a file afterwards must never look like one witnessed as it happened.\n\nCoverage is high despite Codex's hooks being shell-only: Codex has no separate Read, Edit\nor Write tools and does its file work by shelling out. Measured over 60 sessions and 848\ntool calls, `exec` is 94% of them. Full reasoning, including two positions this project got\nwrong first, is in [`docs/CODEX_CAPTURE.md`](/fuckbigtech-ai/homestead-memory/blob/master/docs/CODEX_CAPTURE.md).\n\n```\nhsm init   ./my-vault          # scaffold or adopt any markdown folder\nhsm ingest ./my-vault          # index it (hybrid BM25+vector via qmd, optional)\nhsm ask    \"what did I decide about X?\"\nhsm ask    \"what did I decide about X?\" ./my-vault --budget 1200 --json\nhsm search \"what did I decide?\" ./my-vault --retrieval balanced --json\nhsm qmd start                  # persistent loopback runtime; no shared global index\nhsm verify ./my-vault          # the integrity gate — the whole point\nhsm distill ./my-vault         # optional: build the cited, verifiable fact layer\nhsm history <note> --as-of 2026-06-01   # what was true THEN (temporal layer)\nhsm serve                      # local HTTP API (auth'd, loopback-only)\n```\n\nPython agents can use the SDK directly:\n\n``` python\nfrom homestead_memory import connect\n\nmemory = connect(\"~/my-vault\", agent=\"my-agent\")\nmemory.remember(\"user\", \"city\", \"Berlin\")\nmemory.ask(\"what city is the user in?\")\n```\n\nThe local HTTP API is documented in [`docs/openapi.yaml`](/fuckbigtech-ai/homestead-memory/blob/master/docs/openapi.yaml).\n\nHomestead keeps qmd in dedicated cache and config directories. It never runs\nmaintenance against qmd's global index. Every structured result reports `engine`,\n`retrieval_mode`, `degraded`, `reason`, `elapsed_ms`, and `index_age_seconds`.\n\n| profile | behavior | use | \n|---|---|---|\n| `fast` | BM25 only | exact names, paths, and low-latency probes | \n| `balanced` | lexical + vector, no LLM reranker | hooks and normal agent context | \n| `quality` | lexical + vector + reranker | explicit high-value research queries | \n\nThe route is persistent qmd MCP, then the dedicated qmd CLI, then a read-only\ndirect scan. Run `hsm qmd doctor`, `hsm qmd refresh`, and `hsm qmd status` to inspect\nthe runtime without touching any other qmd collection.\n\nRefresh is explicit and incremental. It writes an atomic checkpoint beneath\n`.hsm/refresh-state.json`, refuses foreign or unhealthy QMD runtimes, emits a\nlive heartbeat while QMD works, and commits the vault fingerprint only after\nembedding reaches zero pending vectors. Reads never trigger an implicit\nrefresh; if QMD is unavailable, retrieval falls back to a read-only scan and\nreports the degraded engine and reason.\n\nFor a Linux/systemd reference deployment, see `deploy/reference/`.\n\nRouters can swap the served model while homestead-memory keeps the same vault\nunderneath. The model name is just the runtime argument; provenance is stamped as\n`name@model` in the `agent` field when a write happens.\n\n``` python\nfrom homestead_memory import connect\nfrom homestead_memory.adapters.openai_compat import MemoryChat\n\nmemory = connect(\"~/my-vault\")\n\ndef remember_reply(response, memory, agent):\n    memory.remember(\n        \"conversation\",\n        \"last_reply\",\n        response.choices[0].message.content,\n        source=\"chat\",\n        agent=agent,\n    )\n\nchat = MemoryChat(openai_compatible_client, memory, remember_fn=remember_reply)\nchat.create(model=\"claude-sonnet-4.7\", messages=[{\"role\": \"user\", \"content\": \"brief me\"}])\nchat.create(model=\"glm-4.7\", messages=[{\"role\": \"user\", \"content\": \"continue\"}])\n\nmemory.history(\"conversation\")  # agents include assistant@claude-sonnet-4.7 and assistant@glm-4.7\n```\n\nLiteLLM can use the same pattern with a pre-call injection helper and a success logger:\n\n``` python\nfrom homestead_memory import connect\nfrom homestead_memory.adapters.litellm_memory import MemoryLogger, inject_memory\n\nmemory = connect(\"~/my-vault\")\nmessages = inject_memory([{\"role\": \"user\", \"content\": \"brief me\"}], memory)\n\n# LiteLLM callback registration style depends on your app setup.\nlogger = MemoryLogger(memory, agent_name=\"assistant\")\n```\n\nMCP already sits above harness-level routers. In a `claude-code-router`-style\nsetup that swaps the backend model, homestead-memory keeps working with\nzero config because memory is external to the model. `history()` and `verify`\nthen attribute every recorded fact to the exact `name@model` that wrote it.\n\nAdapters target the public framework interfaces listed here as of the current releases and may need version bumps as those APIs evolve. Core remains stdlib-only; install only the extra for the framework you use.\n\nUniversal tools work with any orchestrator that can register callables or JSON-schema function tools:\n\n``` python\nfrom homestead_memory import connect\nfrom homestead_memory.adapters.tools import recall_tool, remember_tool, tool_specs, verify_tool\n\nmemory = connect(\"~/my-vault\", agent=\"my-agent\")\ntools = [remember_tool(memory), recall_tool(memory), verify_tool(memory)]\nspecs = tool_specs(memory)  # name, description, parameters\n```\n\nLangGraph `BaseStore` (targets `langgraph>=0.2`):\n\n``` python\nfrom homestead_memory import connect\nfrom homestead_memory.adapters.langgraph_store import HomesteadStore\n\nstore = HomesteadStore(connect(\"~/my-vault\", agent=\"langgraph\"))\ngraph = builder.compile(checkpointer=checkpointer, store=store)\n```\n\nCrewAI storage/memory (targets `crewai>=0.70`, storage-style\n`save/search/reset`):\n\n``` python\nfrom homestead_memory import connect\nfrom homestead_memory.adapters.crewai_memory import HomesteadCrewAIStorage\n\nstorage = HomesteadCrewAIStorage(connect(\"~/my-vault\", agent=\"crewai\"))\nstorage.save(\"Researcher found the supplier shortlist\", metadata={\"task\": \"supplier_shortlist\"})\n```\n\nAutoGen `autogen_core` Memory protocol (targets `autogen-core>=0.4`):\n\n``` python\nfrom autogen_core.memory import MemoryContent, MemoryMimeType\nfrom homestead_memory import connect\nfrom homestead_memory.adapters.autogen_memory import HomesteadAutoGenMemory\n\nmemory = HomesteadAutoGenMemory(connect(\"~/my-vault\", agent=\"autogen\"))\nawait memory.add(MemoryContent(content=\"Use metric units\", mime_type=MemoryMimeType.TEXT))\n```\n\nOpenAI Agents SDK Session protocol or function tools (targets\n`openai-agents>=0.0.1`):\n\n``` python\nfrom homestead_memory import connect\nfrom homestead_memory.adapters.openai_agents import HomesteadSession, function_tools\n\nmemory = connect(\"~/my-vault\", agent=\"openai-agents\")\nsession = HomesteadSession(memory, session_id=\"user-123\")\nagent_tools = function_tools(memory)\n```\n\n**Claude Code / Desktop / Cursor** (MCP: memory tools, not action capture):\n\n```\nclaude mcp add homestead-memory -- hsm mcp ~/my-vault\n# tools: memory_ask · memory_search · memory_verify · memory_history ·\n#        memory_ingest · memory_distill\n```\n\n\"Runs on your device\" is table stakes now. Every memory tool stores locally.\n**Nobody verifies.** Memory rots quietly: a note contradicts itself, an extracted\n\"fact\" loses its source, a body drifts past its own changelog, the current value\ngets shadowed by a stale one. You find out weeks later, when your agent confidently\ntells you something that stopped being true in March.\n\nRot is only the *passive* failure. Memory also gets **tampered** with (a fact\nedited after it was written) and **poisoned** (untrusted input injects a \"memory\"\nthat was never true, a named 2026 attack class). homestead-memory catches all\nthree mechanically. Sign the vault and any edited byte breaks the signature. A\ndistilled claim must cite a source that resolves, or it is dropped. Recall benchmarks\nmeasure whether the model *remembers*. **RotBench measures whether the memory can be\ntrusted.** See [`benchmarks/ROTBENCH.md`](/fuckbigtech-ai/homestead-memory/blob/master/benchmarks/ROTBENCH.md).\n\nhomestead-memory is built around three commitments:\n\n1. **Markdown-primary.** The human-readable files ARE the memory. Indexes and\nprojections are derived and disposable. You can leave any time. It is your folder.\nImport/export Google's**Open Knowledge Format** (`hsm export --format okf` ) plus\nMem0/Zep: we are OKF, but signed and verifiable.\n2. **Verification over trust.** Integrity is a*number* (RotBench, 0–100), computed\nby mechanical checks, with no LLM judging its own homework. See[`benchmarks/ROTBENCH.md`](/fuckbigtech-ai/homestead-memory/blob/master/benchmarks/ROTBENCH.md) .\n3. **Auditable extraction.** The optional distilled layer ([`docs/DISTILL_SPEC.md`](/fuckbigtech-ai/homestead-memory/blob/master/docs/DISTILL_SPEC.md) )\nextracts entity facts*with verbatim quotes, checked in code* . A claim either\ncites a real source or it is dropped. Contradictions append a changelog line\n(`update current_crm: \"Salesforce\" -> \"HubSpot\" (source: chat-042.md)` ), never a\nsilent overwrite. Extraction you can audit is extraction you can trust.\n\n|  | extraction camp (Mem0, Zep) | verbatim camp (MemPalace, **this** ) | \n|---|---|---|\n| write cost | LLM call per turn/episode | **$0** (embed only; distill optional) | \n| information | lossy summaries | **lossless** raw text | \n| auditability | trust the extractor | **cite-or-drop, checked mechanically** | \n| integrity score | — | **RotBench, published every run** | \n\nMeasured on the full 500-question `_s` set (48-session haystacks with distractors),\nscored with the **official per-type judge methodology**, reader `glm-5.2`,\nindependent judge `deepseek-v4-pro`. Reproduce: [` benchmarks/README.md`](/fuckbigtech-ai/homestead-memory/blob/master/benchmarks/README.md).\nFull run history including the failures: [`benchmarks/RESULTS.md`](/fuckbigtech-ai/homestead-memory/blob/master/benchmarks/RESULTS.md).\n\n| metric | value | \n|---|---|\n| retrieval recall@k | **85%** (evidence surfaced into top-k) | \n| QA accuracy (official methodology) | **52.8%** | \n| context tokens / query | **~5.2k** | \n| RotBench | **99.4 / 100** (integrity of the*constructed benchmark vault* , not a claim about arbitrary vaults) | \n\nRead the RotBench row carefully: it scores the vault this harness builds, so it is a\nstatement about *this* run, not a promise that your vault will score 99. Pointed at a\nreal working vault it usually will not, and that is the tool doing its job. See\n[`benchmarks/ROTBENCH.md`](/fuckbigtech-ai/homestead-memory/blob/master/benchmarks/ROTBENCH.md) for what the score does and does\nnot measure.\n\nWhat we will and will not claim: recall is elite and *reader-independent*. QA is honest\nand mid. Published systems self-report higher on their own harnesses (Mem0 94.4%,\nZep 63.8% independent). We publish the harness, the judge, and every failed\nexperiment instead. No number here is from a harness you cannot run yourself.\n\n- **Cross-platform.** Pure Python except for`cryptography` , which does the signing.\nCI: ubuntu / macos / windows.**The pack's bundled verifier stays stdlib-only** , so a\nrecipient checks a pack with nothing installed, which is the part that matters.\n- **Degrades explicitly.** qmd 2.1+ is optional. MCP failure falls back to the\ndedicated CLI; qmd failure falls back to a read-only scan. Machine-readable output\nnames the engine and reason instead of silently pretending the fast path worked.\n- **Local by default.** The HTTP API binds loopback with bearer auth + DNS-rebind\nprotection; the MCP server is stdio (client-spawned). Nothing phones home.\n- **Temporal.** Changelog lines make history queryable:`hsm history note --as-of DATE` .\n\nv0.4, building in public. Roadmap: [`ROADMAP.md`](/fuckbigtech-ai/homestead-memory/blob/master/ROADMAP.md). Break our benchmark:\n[`benchmarks/ROTBENCH.md`](/fuckbigtech-ai/homestead-memory/blob/master/benchmarks/ROTBENCH.md). Adversarial fixtures get merged.\n\nMIT © Kinetic Labs Inc. · a [FuckBigTech](https://fuckbigtech.ai) / HOMESTEAD project.", "url": "https://wpnews.pro/news/show-hn-claude-code-hooks-that-log-every-tool-call-124ms-per-call", "canonical_source": "https://github.com/fuckbigtech-ai/homestead-memory", "published_at": "2026-09-10 18:15:50+00:00", "updated_at": "2026-09-10 18:37:26.174549+00:00", "lang": "en", "topics": ["ai-agents", "ai-tools", "ai-safety", "ai-policy", "developer-tools"], "entities": ["Claude Code", "homestead-memory", "IETF", "EU AI Act", "draft-sharif-agent-audit-trail-01", "IPR disclosure 7558", "Ed25519", "ECDSA P-256"], "alternates": {"html": "https://wpnews.pro/news/show-hn-claude-code-hooks-that-log-every-tool-call-124ms-per-call", "markdown": "https://wpnews.pro/news/show-hn-claude-code-hooks-that-log-every-tool-call-124ms-per-call.md", "text": "https://wpnews.pro/news/show-hn-claude-code-hooks-that-log-every-tool-call-124ms-per-call.txt", "jsonld": "https://wpnews.pro/news/show-hn-claude-code-hooks-that-log-every-tool-call-124ms-per-call.jsonld"}}