Access Guard
Mark the paths an AI agent must never read, never write, or may only touch after you say so β and have every C3 surface enforce it. Four rule kinds in your project (or global) config, one evaluator, refusals that tell the agent exactly why and what to do instead.
What this is β and is not. Access Guard protects a cooperative agent against mistakes and prompt-injection: an agent that names a protected path gets a firm, reasoned refusal at every C3 surface. It is not a sandbox. It governs file actions invoked through C3's tools and hooks, but does not isolate raw OS processes or external editors. See Coverage for exactly where each layer holds.
Quick start
Add a rule
UI β project β Access Guard tab β Add rule, or:
c3 access add "secrets/**" --kind deny
Or pause instead of block
A confirm rule lets writes through only after you
approve them, one at a time:
c3 access add "infra/**" --kind confirm
Test a path
Use the tab's Test path probe, or:
c3 access check secrets/key.txt
Let it work
A deny/read_only attempt returns a firm
[c3-access:denied] refusal. A confirm write
pauses and asks you. See Confirm β ask me
first.
Rules & semantics
Rules live in the access section of
.c3/config.json (project scope) and
~/.c3/config.json (global scope):
{
"access": {
"deny": ["secrets/**", "*.pem"],
"read_only": ["docs/legal/**", "migrations/**"],
"confirm": ["infra/**", "*.tf"]
}
}
| Rule kind | Read | Write / create / delete | Appears in search? |
|---|---|---|---|
| deny | β | β | β (never listed or hinted) |
| read_only | β (unless another rule denies) | β | β |
| confirm | β (reads are never gated) | pauses for your approval | β |
There is a fourth kind, mask β reads stay open but come
back transformed. It's its own subsystem with its own guide:
Mask Guard.
- Precedence:
deny > mask > read_only > confirm. Confirm is the loosest non-allow outcome: a hold can be approved into a write, aread_onlycannot. Everything stricter wins when rules overlap. A user'sread_onlyrule is never softened into a pause by aconfirmrule beside it, and mask beats confirm because an edit made against a transformed view can never be trusted against the real file. - confirm is write-only, by design. There's no
read-confirm knob. Pausing on read already exists as
denyplus an Override Request. A confirm hold files that request for you automatically; see Confirm β ask me first. - Tighten-only. There is no
allowlist. Scopes merge as a union β a rule anywhere applies everywhere. A project config can only add protection, never grant access; an unknown key in the section is a hard config error. - Globs are POSIX-style, case-insensitive.
**crosses directories,*does not; a pattern without/matches the file name at any depth (*.pemmatchesa/b/server.pem). - deny also means deny-create. Path manipulation and OS aliases (short names, trailing dots/spaces, stream syntax) are normalized before matching, so alternate spellings of a protected name cannot slip past the rule.
- deny also means deny-enumerate. Search, maps, and
listings never reveal a denied file's existence. When any rules are
active, search output carries a
[c3-access:limited]footer so the agent doesn't conclude "missing" means "doesn't exist". - Rule text is not secret. Refusals and
c3_statusshow the matched glob β the guard protects file contents and existence, not the rule list. Name globs accordingly. - All rule changes are human-only β UI or CLI, and every mutation is logged to the project's edit ledger. Agents have no tool that can add, remove, or loosen rules.
- Local-only in v1.
.c3/is gitignored: rules do not travel with a cloned repo.
Built-in protections
These apply in every C3 project with no configuration. Tier 0 is
absolute: nothing turns it off. Tier 1 is on by default, but each guard
now takes an independent mode: deny
(tighten to a full block), confirm (pause and ask),
allow (off), or default (the shipped
behaviour). If the access config is corrupt or unparseable, that scope
fails closed β everything in it is denied until fixed.
Tier 0 β absolute, no mode, no opt-out
| Paths | Protection | Why |
|---|---|---|
| .c3/secrets.enc | deny | Credential vault, encrypted at rest |
| .c3/cred_state.json | deny | Credential registry sidecar |
| .c3/vault_backup.json | deny | Passphrase-sealed vault backup (v2.145.0) |
These already carry their own guard (the vault's own encryption) and
their own human-only escalation, so an opt-out here would only be a
shorter route to the same secrets. c3 access builtin mode
refuses to touch them at any scope, and even an approved
**/.c3/** write or a confirm-mode
.c3/** can never reach them β see
Override Requests.
Tier 1 β mode-governable
| Paths | Default mode | What each mode does |
|---|---|---|
| **/.env* | deny (all ops) | confirm pauses every op, not just writes β a
write-only confirm on a full-deny guard would silently become
allow-read. allow turns it off. |
| **/.c3/** Β· **/.claude/settings*.json Β· **/.git/** Β· **/.grok/hooks/** | read_only (write-deny) | deny tightens to a full block; confirm
pauses the write; allow turns the write-deny off.
Reads stay open in every mode here. |
**/.grok/hooks/** is Grok Build's hook registration
(the command lines it runs) β the same role
**/.claude/settings*.json plays for Claude Code.
Two keys, on purpose. Loosening a Tier 1 guard
needs a config entry and a matching keyring attestation β
either alone leaves the builtin enforcing. An agent that manages to
write config.json (exactly the move a prompt-injected one
would try) still cannot produce the attestation. Loosening
**/.env* also prints one advisory line pointing at the
vault: c3_credentials(action='import_env') reads the file
server-side, so the agent gets names, lengths and fingerprints and
never a value.
# every project on this machine
c3 access builtin mode "**/.env*" confirm
# THIS project only β replaces the global mode for that glob
c3 access builtin mode "**/.c3/**" allow --project
# back to shipped behaviour
c3 access builtin mode "**/.env*" default
A project-scope mode is attested under its own keyring realm, so a
cloned repo that carries access.builtin_mode in its config
changes nothing on a machine that never ran the attestation β the mode
lives in this machine's keyring, not in the checkout. See
Per-project & the Hub.
Tier 1, confirm-by-default β the agent-config surfaces
Until v2.100.0 these files were fully writable β only after-the-fact artifact capture watched them. An agent could add itself an MCP server, rewrite a hook's body, or edit its own instructions, and nothing paused it. Now every write to any of them holds for your one-tap approval; reads stay open, because an agent must always be able to read its own instructions.
| Group | Paths |
|---|---|
| Instructions | **/claude.md Β· **/agents.md Β· **/gemini.md Β· **/AGENTS.override.md Β· **/.cursorrules |
| MCP config | **/.mcp.json Β· **/.vscode/mcp.json Β· **/.cursor/mcp.json Β· **/.codex/config.toml Β· **/.codex/hooks.json Β· **/.gemini/settings.json Β· **/.grok/config.toml |
| Claude Code bodies | **/.claude/hooks/** Β· **/.claude/skills/** Β· **/.claude/agents/** Β· **/.claude/commands/** Β· **/.claude/plugins/** |
| Other IDEs | **/.github/copilot-instructions.md Β· **/.grok/skills/** Β· **/.grok/agents/** Β· **/.grok/rules/** |
**/.claude/settings*.json stays in the harder Tier 1 row
above, not here: hook registration stays a full write-deny
while hook bodies pause, because registration is what decides
code execution. This tier is mode-governable too: deny
hardens it to a write-deny and allow restores the pre-2.100
behaviour, through the same c3 access builtin mode. See
Confirm β ask me first for what actually happens
when a write here pauses.
Two more paths stay a plain, non-configurable write-deny: the C3 install directory (agent self-modification, installed layouts only) and the credential-vault write guard, which is unconditional and keys off a fixed tool set β no mode reaches it.
*.pem, id_rsa* and *.key are
not builtins β they're a default rule seeded into global scope
on install, visible in c3 access list and removable like
any other rule.
Confirm β ask me first
A confirm rule is the middle mode between "always
allowed" and "always refused": the agent may change these files, but
every change goes past you first. Reads are untouched. A write refuses
softly, and C3 itself (not the agent) files an Override Request the
moment the write is blocked.
c3 access add "infra/**" --kind confirm
What happens when a write hits a confirm rule
- The write is refused with tag
[c3-access:confirm]instead of your file being touched. - C3 auto-files the request. The agent cannot
forget to ask and cannot word the ask β the card is built from the
trusted denial (tool, operation, path, rule), never from anything
the agent composed. This happens at the PreToolUse hook for native
tools and inside
c3_edit; every otherc3_*tool refuses without filing and says so. - You decide on your phone, in the Hub, or with
c3 override approve <id>β see Override Requests for all three. - One tap mints a single-use, session-bound, path-exact grant. The
agent retries the exact same call, on the same surface β a
grant matches on tool, so a hook-filed native
Edithold is not satisfied by ac3_editretry.
The refusal names the request and tells the agent what to
do β wait on the id (c3_override(action='wait',
request_id=..., timeout_s=180)), that a "still pending" answer
is not a denial, and, when no request could be filed, to follow that
reason's own instruction (a deny+mute means don't ask again; a rate
limit means withdraw or wait) rather than defaulting to "ask in
chat."
Precedence
confirm is the loosest non-allow outcome:
a hold can be approved into a write, a read_only cannot.
So deny > mask > read_only > confirm. A user's
read_only rule is never shadowed by the agent-config
confirm tier, and a confirm rule you write over **/.c3/**
does not outrank the Tier 1 **/.c3/** write-deny. Use
c3 access builtin mode "**/.c3/**" confirm for that instead
(Built-in protections).
access_confirm defaults on
Override Requests as a whole default off β a project has to opt in
before an agent may ask for anything. Confirm rules are the one
deliberate exception: writing a confirm rule is itself the
opt-in, so the access_confirm layer defaults
on and doesn't need override.enabled. Turn
it off explicitly with override.layers.access_confirm: false
if you want confirm rules to refuse silently, with no auto-filed
request, instead.
Shell writes pause too
A confirm rule (and read_only, and the Tier 1
write-denies) governs the write class, and since v2.102.0 that includes
c3_shell and native Bash, not
just c3_edit. echo >> CLAUDE.md,
sed -i on .mcp.json, or a heredoc into a hook
body all evaluate the files the command writes as a write and pause the
same way an editor write would. See
Shell command scan for how the scan finds
those targets, and its limits.
Override Requests
Every guard on this page is terminal by default: the only way past a block used to be a human weakening a rule permanently. Override Requests add the missing primitive β a grant that lets one blocked call succeed once, without touching policy. The rule that denied the call is still in force the instant the grant is spent.
Off by default, one layer excepted. Every
escalatable layer defaults to off. A project must opt in with
override.enabled and turn on the specific layer, except
access_confirm, which is on because a human writing a
confirm rule already is the opt-in.
What can ever be escalated
| Layer | What it covers |
|---|---|
| discipline | Native Edit/Write blocked by tool discipline (not path policy) |
| access_readonly | A read_only rule |
| access_deny | A user deny rule β needs the glob retyped by hand to approve |
| access_builtin | A Tier 1 builtin β same typed-glob challenge |
| access_confirm | A confirm rule β one tap, no typed challenge (default ON) |
| mask | Not really escalatable in practice β a masked path is a different view being served, not a refusal waiting to be lifted |
| shell_warn | The c3_shell soft-warn caveat β replaces the warning line, never unblocks the catastrophic-command tier |
Never escalatable, at any setting, by any approval:
the credential vault, .c3/secrets.enc,
.c3/cred_state.json, .c3/vault_backup.json,
the Tier-0 absolute denies, the dispatcher's fail-closed deny, and the
catastrophic c3_shell blocks. A denial from one of those
never reaches the grants file, and the refusal never mentions that a
request surface exists.
The loop: ask, decide, retry
Agent asks
Blocked, escalatable, opted-in: the agent calls
c3_override(action='request', path=..., why=...). Rate
limited: 3 pending per session, 20/hour per project. A
duplicate ask returns the existing card.
You decide
Phone, Hub, or CLI β see below. Approving access_deny
or access_builtin needs the rule glob retyped by hand;
every other layer is one tap.
Agent retries
The grant is single-use, session-bound, path-exact and
short-lived (15 min by default). The agent must wait or poll
(c3_override(action='wait', request_id=...,
timeout_s=180)), then retry the exact call once.
The agent's tool β c3_override
| action | Does |
|---|---|
| request | File a request for one blocked path (+ op, why) |
| status | Check a request_id without blocking |
| wait | Block up to timeout_s (default 60, max 180) for a decision |
| list | This session's requests |
| withdraw | Cancel a request this session filed |
There is no approve action, anywhere in this
tool, on purpose. Asking for one by name gets a refusal that
says so β approval is human-only, from the phone, the Hub, or
c3 override approve.
Who answers
- Desktop CLI β
c3 override requestslists pending cards;c3 override approve <id> [--mode once|session|rule] [--confirm <glob>]andc3 override deny <id> [--mute]decide. - Phone β the mobile app's inbox, Bearer auth on
every route, an inbox across every project. The typed-confirm
challenge is the rule glob itself, not a nonce: retyping
**/.env*costs more than a habit-tap. A requested TTL is clamped and the client is told when it was. - Desktop Hub β the Access tab, pending cards across every project on the machine, the same typed-glob challenge re-enforced server-side. Under the cards, a read-only policy matrix shows any project's effective rules and override-layer switches. See Per-project & the Hub.
Mute
c3 override deny <id> --mute denies and
suppresses identical requests (same project, session, layer, rule,
tool, operation and path) for the rest of the session. A new session
gets to ask again. The mute store fails open (the opposite of
the grant store): a lost mute costs one extra notification, never a
capability.
Session and rule-scoped grants
The default grant (mode=once) covers one exact call.
Two looser modes exist, both opt-in and both needing their own typed
confirmation:
| Mode | Covers | Needs | Lifetime |
|---|---|---|---|
| once | This exact tool + path, one use | Nothing extra | Up to 15 min |
| session | Unlimited uses of this exact call, this session | override.allow_session_grants + retype session | Up to 15 min |
| rule | Every path the rule matches, any tool in the same op class (read/write/shell) | override.allow_rule_grants + retype the rule glob | 4h default, 8h hard ceiling, or 30 min idle β whichever comes first |
A rule grant relaxes exactly two of the nine match conditions: the
exact tool becomes any tool in the same declared class (an unclassed
tool, like c3_artifacts or c3_project, never
widens), and the exact path becomes the path set the rule glob
describes. It still cannot reach a Tier-0 file or the override
policy/grant store, even under a **/.c3/** rule β proven
live: such a grant covers .c3/notes.txt and never
override_grants.json. Rule grants are visible and killable,
not just logged: the Hub's Access tab lists active grants with what
each one reaches, and can revoke one.
What it costs you
A rule that keeps generating requests is a signal, not just noise.
c3 override costs [--days N] [--all] and the Hub's
"costing you" strip surface it: how many times a rule held the agent in
the trailing window, how you answered, and a suggestion β
convert to allow (mostly approved), tighten or deny
(mostly denied), or review.
c3 override costs --days 7
Per-project control & the Hub
Two things used to be global-only and are now settable per project: which builtin guards apply, and what override policy a project runs. A third is new outright: editing Access Guard rules across every registered project from one screen.
A project can loosen (or tighten) its own builtins
Global scope used to be the only place that could change a Tier 1
builtin's mode. A project-scope mode now replaces the
global one for that glob, loosening included β attested under its own
keyring realm (builtin_mode|proj|<path>|<glob>),
so cloning the repo never carries the loosened mode to a machine that
never attested it.
c3 access builtin mode "**/.env*" confirm --project
c3 access list # prints [project] / [global] beside each mode
Inherit again, or see one project's whole policy in one read
Setting a mode or an override policy at project scope used to be a
one-way trip: nothing meant "go back to whatever global says" except
hand-editing the config out. c3 override policy and the
enforcement mode now both take a clear, which drops the
project's own section and falls back to global (and global falls back
to the built-in default). The Hub reads a project's whole effective
override policy for every key, whether it's corrupt or even
initialized, in one call instead of one request per key.
Access Guard rules across every project, from the Hub
The Hub's Access tab can add or remove a deny /
read_only / confirm rule on any registered
project, or on global scope, without opening that project. Adding
tightens, so it needs no confirmation; removing a deny
rule, or any global-scope rule, needs the glob retyped as a
confirmation. A folder with no .c3/ is refused rather than
silently initialized β a rule C3 does not read protects nothing while
claiming to. Every real write lands in the target project's own edit
ledger.
"May agents ask?"
The Hub's Access tab also exposes override.enabled
itself (the switch that decides whether a request may exist at all),
with the seven escalatable layers listed individually and the same two
caveats always on screen: policy merges by tightening only, and the
credential vault and the catastrophic blocks are never escalatable at
any setting. Turning the feature on, or turning a layer on, needs a
typed confirmation; turning either off never does.
Shell command scan
c3_shell and native Bash (via the
PreToolUse hook) both read a command's tokens and judge each one
against Access Guard, per command segment (split on
&&, ||, ;,
|, and newlines) so a leading cd updates
which directory later segments resolve against. Reads are checked
against deny-kind rules only; since v2.102.0, the files a
command writes (redirects, tee,
sed -i, cp/mv, inline Python
open(...,'w')) are extracted and evaluated as writes, so a
read_only, confirm or write-deny rule holds a
shell write exactly like an editor write would.
Best-effort, stated plainly in the refusal. Subshells, variables and globs are invisible to a token scan. A clean scan is not enforcement β it is mistake-and-injection containment. It errs toward under-flagging: a token it misses is still caught by the real path check on the tool that follows; a token it flags wrongly is a hard deny on a command that may name no file at all.
Spelling rules (no glob β a refusal cites these by name)
| Name | Means |
|---|---|
| <unc> | UNC / network path outside the project |
| <unresolvable> | Path could not be resolved |
| <empty-component> | A path component empties after normalization |
| <8.3-alias> | 8.3 short-name component on a non-existing target |
| <ads> | NTFS alternate-data-stream syntax (Windows only) |
<ads> caused the most false positives while the
scan matured, all fixed and worth knowing if a command you expect to
run gets denied by a rule c3 access list doesn't show:
- URLs and IPv6 (v2.64.0). A residual colon reads
as NTFS stream syntax, so
https://example.comandfc00::/7used to hard-deny any command that mentioned one βgit commitmessages about networking,curl,gh pr create. Scheme-prefixed and IPv6 literal/CIDR tokens are now skipped before evaluation. - Pytest node ids and git revspecs (v2.76.2).
a/b.py::Thingis a node id, never a stream. But a genuinefile::$DATAstream still denies, because the discriminator is the$-prefixed type slot. A git<rev>:<path>revspec now has its path half checked instead of the whole token βgit show HEAD:.envstill denies on.env's own rule. - The revspec rewrite, for real commands (v2.76.3).
The git-revspec fix above only fired when a command started with
gitβcd /repo && git show origin/main:pyproject.tomlwas still denied, because that's how the idiom actually arrives. Segment-by-segment fixed it. - The cwd the command actually runs in (v2.77.0).
The existence gate judged every path against the session's project
root even after a
cdmoved somewhere else, socd <elsewhere> && cat .envwas not flagged. Fixed alongside the segment-aware rewrite above β and as a result, a project-scoped rule now correctly stops binding once youcdinto a different repo.
Refusal messages
Every denial carries a stable machine tag, the matched rule and scope, and explicit no-retry guidance β so the agent pivots instead of retry-looping or silently recreating "missing" files.
| Tag | Meaning |
|---|---|
| [c3-access:denied] | Operation refused by a deny rule (or a write refused where read_only applies via hooks). Policy, not an error β the agent is told not to retry or route around it. |
| [c3-access:read_only] | Write refused; reads remain separately evaluated. |
| [c3-access:confirm] | "S8": a pause, not a refusal. Names the auto-filed request id and the exact c3_override(action='wait', ...) call, or says why no request was filed and what to do about it. See Confirm β ask me first. |
| [c3-access:limited] | Search/listing footer: results may omit protected paths β a file that doesn't appear may still exist. |
| [c3-access:error] | The enforcement hook itself failed β native write tools are denied until it loads (fail-closed). See Troubleshooting. |
| [c3-override:granted] | An approved grant covered this retry β the caveat line an agent sees in place of a block. |
| [c3-override:spent] | A grant existed but was consumed by a concurrent call between the check and the write; the retry is refused rather than let through ungranted. |
| [c3-override:not-escalatable] | A c3_override(action='request') for a layer that can never be asked about (vault, Tier-0, catastrophic shell). Never shown for a denial that didn't offer to escalate. |
Masked-path tags ([c3-mask:transformed],
[c3-mask:limited], [c3-mask:unsupported]) are
documented on the Mask Guard page.
Coverage β honest limits
Enforced: C3 MCP tools (any agent that uses C3:
Claude Code, Codex, Gemini, Grok Build, Oracle-connected models) Β·
Claude Code native tools (PreToolUse hooks, fail-closed) Β·
c3_shell and native Bash (best-effort
per-segment token scan: reads against deny rules, writes against
every write-class outcome; see Shell command
scan).
Not enforced: a non-Claude agent's raw shell or direct file APIs (including native file tools in non-Claude CLI environments unless they route through C3 tools), external editors, and OS-level access. Renames/moves via shell commands are a known v1 gap. In an IDE with no PreToolUse hooks (Codex, Antigravity, Copilot) only the c3_* tools are covered; a native write there is not intercepted. Access Guard is a guardrail for cooperative agents. Pair it with OS permissions when you need a real boundary.
Tool discipline is a separate knob
Access Guard governs paths. A neighboring layer, tool
discipline (c3 enforce), governs whether native
Edit/Write are blocked in favour of
c3_edit at all β a workflow preference, not a security
boundary. Loosening it never reaches Access Guard rules, the credential
vault, or agent locks; those enforce at every discipline mode.
c3 enforce # show the current mode
c3 enforce advisory # allow native writes, with a nudge
c3 enforce strict # block until a c3_* call ran first
c3 enforce off # no nudging
c3 enforce --global advisory # default for every project on this machine
c3 access stats labels each denial with its layer
(path policy vs. tool discipline) and names the exact command that
clears it. Full reference: docs/enforcement.md.
CLI commands
# rules
c3 access list # rules by scope, builtins, coverage
c3 access add "secrets/**" --kind deny # project scope
c3 access add "infra/**" --kind confirm # pause writes, don't block them
c3 access add "*.pem" --kind deny --global # global scope
c3 access remove "secrets/**" --kind deny
c3 access check secrets/key.txt # probe: verdict + matched rule
c3 access check docs/x.md --op write
c3 access stats # what got denied, how often, how to clear it
c3 access stats --clear
# builtins
c3 access builtin mode "**/.env*" confirm # deny | confirm | allow | default
c3 access builtin mode "**/.c3/**" allow --project
c3 access builtin disable "**/.git/**" # legacy spelling of mode allow
c3 access builtin enable "**/.git/**"
# override requests (see Override Requests above)
c3 override policy # effective policy + escalatable layers
c3 override requests [--status pending] [--all]
c3 override approve <id> [--mode once|session|rule] [--confirm <glob>]
c3 override deny <id> [--mute]
c3 override list [--session <id>] [--audit N] # live grants
c3 override grant <path> --session <id> [...] # mint one directly, no request
c3 override check <path> --session <id> # would a grant cover this now?
c3 override revoke <grant_id>
c3 override sweep # drop expired/spent grants
c3 override costs [--days 7] [--all]
# Mask Guard β see masking.html
c3 access mask add "data/**" --preset sample_rows --params "count=20,strategy=first"
UI & probe
Project Access tab
Rules grouped by scope (builtins shown with their mode, not just
locked), an add-rule form with all three glob-list kinds, delete with
typed confirmation for deny rules, a mode selector and
scope picker for builtins, and a Test path probe that
shows the verdict, matched rule, and the exact refusal the agent would
see. The coverage matrix is pinned at the bottom of the tab.
Hub Access tab (desktop, cross-project)
- Pending request cards β approve, deny, or
deny+mute, across every project on the machine; typed-glob
challenge for
deny/builtinapprovals. - Costing you β rules generating the most requests this week, with a suggestion and a jump to that rule.
- Active grants β every live grant, what a rule grant's reach actually covers, and a revoke button.
- Rules panel β add or remove a rule on any registered project, or global scope, without opening it.
- Policy panel β read-only view of any project's effective rules and override-layer switches, plus the "May agents ask?" enable toggle and per-layer switches.
The Hub edits policy and rules; it does not replace the per-project
UI β day-to-day rule authoring still happens in the per-project Access
tab or c3 access.
Troubleshooting
| [c3-access:error] on every write | The access hook failed to load β this is fail-closed by design.
Check .c3/hook_errors.log, rerun
c3 install-mcp. C3 MCP tools keep working
meanwhile. |
| Everything in a scope denied | That scope's access section is corrupt or has an
unknown key. Fix the JSON; c3 access list names the
scope. |
| Expected file "missing" in search | Check c3 access check <path> β deny rules
remove files from search entirely (deny-enumerate). |
| Rule not matching | Globs are POSIX-style: use forward slashes; remember
* does not cross directories β use **.
Probe with c3 access check. |
| Agent says "wait" never returns / times out | The default c3_override wait is 60s; the agent
should pass timeout_s=180 and treat "still pending"
as not-a-denial, waiting again rather than giving up. |
| A retry after approval is still refused | A grant matches the tool that filed the request β retry the
same surface (a hook-filed native Edit hold
needs a native retry, not c3_edit). Check
c3 override list for whether the grant still has
uses left. |
| "May I ask?" offer never appears in a refusal | Override Requests are off by default; a project must set
override.enabled and turn the specific layer on
(c3 override policy shows both). The offer line only
ever appears on the hook surface, not on a bare c3_*
refusal. |
| A builtin mode won't stick | Both a config entry and a keyring attestation are required;
c3 access builtin mode writes both together. A
hand-edited config entry with no matching attestation is ignored
and the builtin keeps enforcing its shipped default. |
Relationship to the Credential Vault. Credentials protect values (never in agent context, injected at the subprocess boundary); Access Guard protects files and folders. The vault's registry and sidecars are covered by Access Guard Tier 0, and both systems log every mutation to the same edit ledger.