Gisteren schreef ik over de vierenzeventig dagen die OpenAI's agents doorbrachten in de systemen van Hugging Face. Ik sloot af met een punt dat ik niet hard kon maken: log trajecten in plaats van losse acties, en bepaal vooraf wie die log uit bed mag bellen.
Onder dat punt hoort een gids. Hier is hij.
Begin bij het deel dat mij verraste. Je logt allang. Claude Code schrijft vanaf de dag dat je het installeerde een gedetailleerd verslag van elke sessie naar je eigen schijf, en als je een beetje op mij lijkt, heb je er nog nooit een geopend.
Wat er al op je schijf staat
Drie aparte sporen, en geen daarvan is ooit met veel bombarie aangekondigd.
Het transcript, in ~/.claude/projects/<project>/<session-id>.jsonl. Ik pakte één doodgewone sessie op deze machine en telde: 1.434 records, 467 berichten van het model en 322 van mij, met daarin 146 Bash-aanroepen, 101 leesacties op bestanden en 12 edits. Over alle projecten heen staan hier 1.156 van die bestanden, samen 676 MB aan transcript.
Het schema is het interessante deel. Elk record kan cwd, gitBranch, version en entrypoint bevatten, een permissionMode plus aparte records elke keer dat die modus verandert, isSidechain om werk van subagents te markeren, durationMs, toolUseResult, en een keten van uuid, parentUuid, promptId en requestId waarmee je elke actie terugleidt naar de prompt die hem veroorzaakte.
En dan drie velden waarvan ik niet wist dat ze bestonden: attributionSkill, attributionMcpServer en attributionMcpTool. Komt een aanroep van een skill of van een MCP-server, dan legt het transcript vast welke. Elk gelezen bestand, elk commando, elke aangeroepen server, met het antwoord op "wat riep dit aan" in hetzelfde record.
De prompthistorie, in ~/.claude/history.jsonl. De mijne is bijna 16.000 regels lang en elke regel heeft precies vier velden: display, pastedContents, project en timestamp. Alles wat jij typte, en niets van wat de agent deed.
De bestandssnapshots, onder ~/.claude/file-history/<session>/. Hier staan 104 sessiemappen, 58 MB, met kopieën van bestanden zoals ze eruitzagen vlak vóór een edit. Hieruit zet /rewind bestanden terug, en dit is het enige spoor van de drie dat je laat zien hoe een bestand er eerder uitzag.
Twee dingen moet je weten voordat je hierop gaat vertrouwen. Het is tijdelijk: cleanupPeriodDays staat standaard op 30, en Claude Code gooit oudere sessiedata bij het opstarten weg. En het staat in platte tekst op schijf. De documentatie is er duidelijk over: leest een tool een .env-bestand of print een commando een sleutel, dan belandt die waarde in het transcript. Je auditlog is tegelijk een verzamelbak voor secrets, beschermd door niets anders dan bestandsrechten.
Daarmee is dit spoor prima om een ongeluk te reconstrueren, en precies daarvoor gebruikt het incidentdraaiboek het ook. Tegen iemand die het verslag wil aanpassen is een tekstbestand waar de gecompromitteerde gebruiker zelf schrijfrechten op heeft weinig waard.
Waarom hier niets van in je compliance-feed staat
Anthropic bracht in mei een Compliance API uit. Als je de aankondigingen leest, zou je denken dat het auditvraagstuk nu op organisatieniveau geregeld is.
Kijk liever naar de reikwijdte. De Activity Feed legt inlogacties, chats, bestanden, projecten, beheerhandelingen en platformacties vast, in honderden verschillende soorten gebeurtenissen, binnen een minuut opvraagbaar en zes jaar bewaard. De content-endpoints leveren claude.ai-data: chats, bestanden, projecten, bijlagen en transcripts van Cowork-sessies die in door Anthropic beheerde omgevingen draaien.
Claude Code op je laptop zit er niet in. Niet de Bash-commando's, niet de edits, niet de MCP-servers die het aanriep. De andere helft van het enterprise-verhaal, de Claude Code Analytics API, geeft dagtotalen per gebruiker: sessies, toegevoegde en verwijderde regels, commits, en hoeveel voorstellen van de Edit-tool zijn geaccepteerd of geweigerd. Je ziet dat een ontwikkelaar dinsdag vijf edits weigerde. Wat die edits waren, staat er niet bij.
Eén uitzondering werkt juist andersom. De securitydocumentatie van Claude Code zegt dat elke handeling in een cloudsessie voor compliance wordt vastgelegd, en de Compliance API geeft al transcripts van remote sessies terug, inclusief tool_use- en tool_result-blokken, zes jaar bewaard. Let op de reikwijdte: dat endpoint bedient op dit moment alleen sessies waarvan product_surface op cowork_remote staat.
Het mechanisme om de tool-aanroepen van een agent bij een complianceteam te krijgen bestaat dus al en draait. Vandaag wijst het naar Cowork, en volgens de bètanotitie komen er later meer product surfaces bij. De Claude Code op je eigen machine, waar de meesten van ons hem draaien, hoort daar voorlopig niet bij.
Wat de feed je geeft, is een registratie van het control plane: identiteit, configuratie en de levenscyclus van resources. Die registratie stopt zodra er een gesprek begint. De log die je securityteam centraal kan bevragen en zes jaar bewaart, weet dus dat je een sessie opende. De log die weet dat je agent rm -rf draaide, is een bestand in platte tekst op je eigen machine dat zichzelf na dertig dagen wist.
Telemetrie is het echte kanaal, en standaard is alles weggelakt
De brug tussen die twee is OpenTelemetry, en Claude Code ondersteunt dat netjes. CLAUDE_CODE_ENABLE_TELEMETRY=1 plus een OTLP-endpoint levert elke 60 seconden metrics en elke 5 seconden loggebeurtenissen op, waaronder claude_code.tool_result, claude_code.tool_decision, claude_code.permission_mode_changed, claude_code.mcp_server_connection en claude_code.api_error.
Zet het aan en het eerste wat opvalt, is hoe weinig het je vertelt. Inhoud wordt standaard weggelakt, zonder uitzondering. Prompts komen binnen als <REDACTED> met alleen een prompt_length. Antwoorden van het model idem. De details van tools ontbreken ook, en dat is nou juist het onderdeel waar het om draait: zonder OTEL_LOG_TOOL_DETAILS=1 zie je geen Bash-commando's, geen bestandspaden, geen namen van MCP-tools, en aanroepen van plugins van derden komen binnen als de tekst custom of mcp. Je krijgt een betrouwbaar verslag dát er een tool draaide, zonder enige manier om te weten welke.
Dus zet je die vlag om, en vanaf dat moment stroomt elk commando en elk bestandspad dat je agent aanraakt naar je collector. Daarnaast is er OTEL_LOG_TOOL_CONTENT voor de invoer en uitvoer van tools, en OTEL_LOG_RAW_API_BODIES voor de volledige request en response van de Messages API inclusief gespreksgeschiedenis, waarvan de documentatie opmerkt dat je daarmee instemt met alles wat de andere drie vlaggen blootleggen.
Zo ziet die keuze eruit, en hij is bijna binair. Uit, en je auditlog kan een linter niet onderscheiden van het droppen van een database. Aan, en je stuurt broncode, commando's en sleutels naar waar je collector ook maar naartoe schrijft. De tussenweg die de meeste teams willen, namen van tools en bestandspaden zonder de inhoud van bestanden, is ongeveer wat OTEL_LOG_TOOL_DETAILS in zijn eentje geeft, en daar zou ik beginnen.
Twee details om mee te nemen. Beheerders kunnen het OTLP-endpoint vastzetten via managed settings, waarna Claude Code bij het opstarten conflicterende variabelen van de ontwikkelaar weggooit, zodat niemand de export lokaal kan omleiden. En subprocessen erven de OTEL_*-variabelen niet, dus Bash-commando's, MCP-servers en language servers die de agent start, worden niet stiekem meegemeten.
De log die je echt wilt is één hook
Wil je een spoor met je eigen regels, dan zijn hooks het middel. PostToolUse vuurt na elke aanroep, met tool_name, tool_input, tool_use_id en tool_result.
Zelf bouwen is de route die Anthropic voor ogen heeft. De securitydocumentatie van Anthropic zegt onder het kopje teamsecurity dat je Claude Code via OpenTelemetry-metrics moet monitoren en wijzigingen in instellingen met ConfigChange-hooks moet controleren. Er is geen product dat dit voor je doet.
{
"hooks": {
"PostToolUse": [
{
"matcher": "Bash|Edit|Write",
"hooks": [
{
"type": "command",
"command": "jq -c '{ts: now, session: .session_id, mode: .permission_mode, tool: .tool_name, input: .tool_input}' >> ~/.claude/audit.jsonl"
}
]
}
]
}
}Elke hook-payload bevat session_id, transcript_path, cwd, permission_mode en hook_event_name, plus agent_id en agent_type binnen subagents. permission_mode is het veld dat je makkelijk over het hoofd ziet: je eigen log legt vast of de sessie op dat moment in bypassPermissions stond, en dat is de eerste vraag die iedereen achteraf stelt.
Vier andere gebeurtenissen verdienen hun plek. PermissionDenied laat zien wat de agent probeerde en jij weigerde, wat een beter signaal is dan alles wat wél mocht. ConfigChange vuurt met een config_source zodra instellingen of skills veranderen terwijl je bezig bent. FileChanged werkt met letterlijke bestandsnamen die je in de gaten wilt houden, dus .env|.envrc is een struikeldraad van één regel. En SubagentStart geeft je het agent_type plus een agent_id om een sidechain mee te groeperen.
Verzamelen was de makkelijke helft
Alles hierboven is loodgieterswerk, en loodgieterswerk krijg je af. De reden dat ik dit stuk schreef is de vraag erna, en daar bestaat geen instelling voor.
Wie leest die log, en waar wordt iemand voor wakker gebeld?
Hugging Face is de casus, en hun kant van dat incident is bepaald geen horrorverhaal. Hun anomaliedetectie, met LLM-triage over securitytelemetrie, legde signalen naast elkaar die stuk voor stuk op gewone ruis leken, en sloeg aan. Het gat zat tussen zien en stoppen: TechCrunch meldde dat de urgentie nooit werd opgeschaald en het on-callteam nooit werd gebeld. De detectie werkte, en daarna bleef het signaal in een wachtrij staan, omdat er geen regel was die het verder bracht.
Dat patroon is niet nieuw, en er zijn cijfers over. Het rapport van Intezer uit februari, gebaseerd op 25 miljoen alerts van 10 miljoen bewaakte endpoints en 82.000 forensische endpoint-onderzoeken, liet zien dat 1,9% van de endpoint-alerts met lage of informatieve prioriteit een echt incident bleek te zijn. Ruwweg één op de vijftig alerts die niemand geacht wordt te lezen is een echte dreiging, weggezet als ruis en door niemand bekeken.
Intezer verkoopt alert-triage, dus houd bij dat cijfer rekening met de bron. De onafhankelijke variant komt van Mandiant, dat voor M-Trends 2026 put uit ruim 500.000 uur incidentrespons in 2025. De wereldwijde mediane verblijftijd van aanvallers ging omhoog, van 11 naar 14 dagen. Organisaties ontdekten de inbraak in 52% van de gevallen zelf, een echte verbetering ten opzichte van de 43% een jaar eerder. De overige 48% hoorde het van buitenaf.
Vertaal dat nu eens naar een agent. Je agent produceert honderden aanroepen per uur, en elke aanroep ziet er op zichzelf legitiem uit, want je hebt hem die tools met opzet gegeven. Stuur je dat naar een wachtrij die niemand heeft toegezegd te lezen, dan heb je het probleem van die 1,9% nagebouwd met een veel grotere noemer.
Schrijf de escalatieregel dus vóór je de pijplijn bouwt. De mijne heeft drie niveaus en past op een bierviltje. Log alles, goedkoop, in de verwachting dat je het meeste nooit zult lezen. Waarschuw bij een kort lijstje patronen: een geweigerde permissie gevolgd door een andere route naar hetzelfde bestand, een .env die gelezen wordt, een MCP-tool die voor het eerst in deze repository wordt aangeroepen, een sessie die omslaat naar bypassPermissions en daarna aan infrastructuur komt. Bel iemand voor bijna niets, en weet vooraf wat dat bijna-niets is.
De eenheid telt net zo zwaar als de drempel. Toegestaan of geweigerd per aanroep is het verkeerde detailniveau, want zo zie je geen reeks. Vispute en Kadam werken dat argument formeel uit in een paper uit maart waarin ze reasoning provenance voorstellen: uitvoeringssporen en state checkpoints leggen vast wat een agent deed. Waaróm hij daarvoor koos, valt daar in het algemeen niet uit te reconstrueren, dus die afweging moet je op het moment zelf als volwaardig veld vastleggen. Het is een positiepaper met een referentie-implementatie en nog zonder meetresultaten, dus het levert je vooralsnog alleen een richting op. Die richting klopt. Je transcript heeft promptId en parentUuid op elk record staan, en daarmee groepeer je een sessie vandaag al in trajecten, zonder op een standaard te wachten.
Wat je concreet kunt doen
- Open deze week één transcript. Pak een
.jsonluit~/.claude/projects/en lees wat een normale sessie van jou bevat. Je schrijft geen escalatieregel voor een patroon dat je nooit gezien hebt. - Kies je bewaartermijn bewust.
cleanupPeriodDaysstaat standaard op 30. Wil je een spoor voor een onderzoek dat pas in maand twee begint, stuur het dan ergens anders heen, en bedenk bij die keuze dat het platte tekst is. - Zet
OTEL_LOG_TOOL_DETAILSaan en houd het daarbij, tenzij je een concrete reden hebt om inhoud te loggen. Namen van tools en bestandspaden beantwoorden de meeste vragen. Ruwe API-bodies beantwoorden de rest en leveren een veel groter probleem op. - Schrijf eerst het lijstje waarvoor je iemand belt. Drie patronen, afgestemd met degene die de pager draagt. Bouw je de pijplijn vóór dat lijstje, dan krijg je een wachtrij, en van een wachtrij wordt niemand wakker.
Wat mij uit de tijdlijn van OpenAI blijft bezighouden, is dat beide bedrijven het bewijs in handen hadden. De pijplijn van Hugging Face zag de aanval in real time. De transcripts van OpenAI bevatten elke stap ervan, volledig, op hun eigen infrastructuur. Er was toch nog een telefoontje op 20 juli voor nodig, over sleutels die allang vervangen waren, voordat iemand doorhad wat er was gebeurd. In de tweedeling van Mandiant viel het bedrijf dat de agents bouwde in die 48%.
Niemand had een betere log nodig. Ze hadden een afspraak nodig over wie er gebeld wordt.