Headless & Audit
Headless mode and the ask verdict
When caliban runs without a TTY — in CI, in a script, or via caliban -p — there is no interactive modal to present. Any tool call that reaches an ask verdict is handled by NonInteractiveAskHandler:
- Default behavior (no flags):
askbecomes a hard deny. The tool call fails with a permission error message that names a concrete remediation. - With
--auto-allow: everyaskverdict becomesallow. This is equivalent todontAskmode for the duration of the run.
The deny message is tailored to the tool class:
| Tool class | Suggested remediation |
|---|---|
File-edit (Write, Edit, MultiEdit, NotebookEdit) | --permission-mode acceptEdits or a narrow --allow rule |
Bash | --allow 'Bash(<glob>)' for a targeted rule, or --auto-allow (flagged dangerous) |
| Other tools | --allow '<Tool>' or --auto-allow |
Opt-in strategies
Choose the least-permissive option that satisfies the task:
# Allow only file edits (most common CI use case)
caliban -p "update version in Cargo.toml" --permission-mode acceptEdits
# Allow specific git commands
caliban -p "commit and push" --allow "Bash:git *"
# Allow all ask-rule tools (use with care)
caliban -p "run the full refactor" --auto-allow
You can also set rules in the project's permissions.toml so they apply without CLI flags:
[[permissions.rules]]
pattern = "Bash:git *"
action = "allow"
comment = "safe for CI"
The JSONL audit log
Every tool-call decision (allow, deny, or ask) is appended to an append-only JSONL file.
Log location
| Platform | Path |
|---|---|
| Linux | $XDG_STATE_HOME/caliban/permission-decisions.jsonl (default: ~/.local/state/caliban/) |
| macOS | $XDG_DATA_HOME/caliban/permission-decisions.jsonl (default: ~/Library/Application Support/caliban/) |
The audit_log setting controls whether logging is active:
[permissions]
audit_log = true # default; set false to disable
Log format
Each line is a JSON object:
{
"ts": "2026-06-01T14:23:01.123456Z",
"session_id": "s_abc123",
"turn_index": 4,
"tool_use_id": "tu_xyz",
"tool_name": "Bash",
"input_excerpt": "{\"command\":\"git push origin main\"}",
"action": "allow",
"matched_rule": {
"pattern": "Bash:git *",
"action": "allow"
}
}
input_excerpt is truncated to 256 characters and newlines are replaced with spaces.
Log rotation
When the log file exceeds 100 MiB, caliban automatically:
- Renames the current file to
permission-decisions-YYYY-MM-DD.jsonl. - Gzip-compresses the renamed file to
permission-decisions-YYYY-MM-DD.jsonl.gz. - Removes the uncompressed renamed file.
- Opens a fresh
permission-decisions.jsonlfor subsequent writes.
Rotated archives accumulate in the same directory. Remove old .gz files manually when disk space is a concern.
Querying the log
Use caliban perms audit to filter and display log entries:
# All decisions since midnight UTC
caliban perms audit --since 2026-06-01T00:00:00Z
# Only denials for the Write tool
caliban perms audit --tool Write --action deny
# Most recent 50 entries
caliban perms audit --head 50
# Combine filters
caliban perms audit --tool Bash --action allow --since 2026-06-01T00:00:00Z --head 100
Exit code is always 0; an empty result prints (empty).
Hardening with permissions.enforce
The permissions.enforce flag prevents the bypass latch from being used, even when --allow-dangerously-skip-permissions is passed:
[permissions]
enforce = true
With enforce = true, caliban refuses to start if --allow-dangerously-skip-permissions is on the command line or if permissions.default_mode is set to bypassPermissions. This is useful for team or managed deployments where operators want to guarantee that static deny rules can never be overridden.