Log reference
Every ForceField decision is one OpenTelemetry log record carrying an OCSF Detection Finding projection (ocsf.class_uid 2004). Everything on these pages came from running the shipped hooks.
Where the log is written
| Sink | Path | Notes |
|---|---|---|
| File (always on) | ~/.claude/hooks/security.log | JSON Lines, one record per line, mode 0600 in a 0700 directory |
| Rotated copies | ~/.claude/hooks/security.log.1 through .log.7 | Rotates at 8 MiB, keeps 7, so the chain caps at 64 MiB |
| Rotation lock | ~/.claude/hooks/.rotate.lock | |
| macOS unified log | no file path; subsystem com.anthropic.claude-code.hooks | read with log show |
| Linux journald | the journal, SYSLOG_IDENTIFIER=cc-security | read with journalctl -t cc-security |
| Linux without journald | whatever /dev/log is wired to, commonly /var/log/messages | |
| Windows | %USERPROFILE%\.claude\hooks\security.log, plus the Application channel |
The file sink is the archive. It is the only sink whose retention this project controls, and the only one that never truncates or fragments a record. Because the path resolves from $HOME, running a hook with a different HOME redirects the log, which is how the test suites and the capture harness keep fabricated attacks out of your real log.
Every session start writes the live sink inventory, including the resolved path, into forcefield.sinks. That record is how you tell “a sink was down” from “nothing happened”:
jq -c 'select(.Attributes."forcefield.guard" == "session_baseline") | .Attributes."forcefield.sinks"' \
~/.claude/hooks/security.log | tail -1
The pages
| Page | Covers |
|---|---|
| Field reference | The schema: top-level fields, every Attributes key, decision to severity, sinks, credential masking, worked jq queries, known gaps |
| Records by hook | One measured record per hook, for all 23 registrations |
| Platforms | Which platforms run the hooks, what each sink does with the same record, and how to read the log on each |
Read this before writing a detection
- Start with known gaps. Nothing records whether an
askwas approved. Every sink fails silently. Both matter more than any field name. - Hunting for what a guard detected, not what it enforced? Query
forcefield.natural, notforcefield.decision. The two differ whenever config downgraded a finding or a remembered approval waved it through. - Looking for coverage that was switched off? A disabled guard reports below
allow, so no severity-based alert will surface it. Queryforcefield.decision == "off", plusforcefield.suppressedandforcefield.memo_hit, by name. - Forwarding to a SIEM? What a sink receives follows from that sink’s measured confidentiality, not from the platform. At
CONF_ADMIN, the free-text floor, the record arrives whole withcommand.lineandfile.pathincluded: that is the macOS unified log and journald. Below it, atCONF_LOCAL, the free-text fields are replaced byforcefield.withheld_fieldsandforcefield.detail_in, so the detection is visible and its evidence is not: that is a non-journald/dev/logdatagram and the Windows Application channel. - On Windows? The code paths are implemented and tested on POSIX against documented contracts, which is a weaker claim than every other platform here carries. Nothing has ever run on a Windows host. Platforms splits implemented from tested, row by row. Use WSL2, which is a Linux target and is measured.
Quick queries
# High-severity findings, by OCSF severity rather than a fragile text match
jq -c 'select(.Attributes."ocsf.severity_id" >= 4)' ~/.claude/hooks/security.log
# Detections that did not enforce: config downgraded, allowlisted, or remembered
jq -c 'select(.Attributes."forcefield.config_downgraded" == true
or .Attributes."forcefield.suppressed" == true
or .Attributes."forcefield.memo_hit" == true)' ~/.claude/hooks/security.log
# Which guard drives your friction, most frequent first
jq -r '[.Attributes."forcefield.guard", .Attributes."forcefield.decision"] | @tsv' \
~/.claude/hooks/security.log | sort | uniq -c | sort -rn
# Everything the git guard prompted on: the clone-time RCE surface
jq -c 'select(.Attributes."forcefield.guard" == "git_guard")' ~/.claude/hooks/security.log
Full recipe set, including timeline ordering across rotations: field reference.