CodeepCodeep

Approvals & Audit

Two halves of the same idea. The audit record says what an agent did after the fact. Remote approval lets you answer, from anywhere, the question it stopped to ask.

The audit record

Every agent run appends to .codeep/audit/ in the project — one JSON line per event, one file per day. Read it back with /audit:

$ /audit
✓ 24 Aug, 16:26 · Git Only (git) · 1s
inspect the repo
command, refused
⨯ refused: read_file → secrets.env

The refusal on that last line is the reason this exists. A capability boundary that nothing records is a boundary you have to take on faith — the run looks identical whether the agent stayed inside it or was never tested against it.

What it does and does not keep

RecordedNot recorded
File paths, commands, URLsFile contents, ever
Reads, writes, searches, fetchesAnything from a project you did not run in
Tool calls the boundary refusedYour prompts to other agents
Which custom agent ran, and what it was granted

Contents are deliberately absent. Undo already keeps them — that is what /undo and checkpoints are for. This record answers a different question, and a record you might hand to someone else should not carry your source with it.

Treat it like shell history. A command line can contain a secret somebody typed into it, and this file keeps command lines. It sits under .codeep/, which most projects already ignore — but Codeep never edits your .gitignore, so check that yours does.

On every surface, in one place

The record lives with the project, not with the app that wrote it — same directory, same format, same field names everywhere. A project you work on from the terminal on Monday and the Mac app on Tuesday has one history, and either can read the other's runs.

Reading itTurning it off
CLI/audit/audit off
Mac appOpen the project in the CLI, or read .codeep/audit/ directlySettings → Tools & Privacy → Audit record
VS CodeCodeep: Show Audit RecordUses the CLI's setting

Records already written are kept. Nothing is deleted by turning it off.

Approval on your phone

When a run stops on a dangerous tool it waits — and if you have walked away, it waits for as long as you are gone. Connect Telegram and the same question arrives on your phone with the same three answers. Answer in either place; whichever comes first decides, and the other is taken down rather than left showing buttons that no longer do anything.

Both the CLI and the Mac app. This is where Telegram earns its keep: the Mac can carry approvals through your own iCloud, but Linux and Windows have nothing of the sort, and the bot API is polled outbound — no server to host, no inbound port. In the terminal the question appears whenever Agent Confirmation would have asked, which by default is before every dangerous tool.
Interactive runs only. A headless run — codeep review --fix, the ACP server, CI — has nobody to ask, and blocking one on an answer that cannot come would hang a pipeline. Those decide from agentConfirmation as before.

Setting it up

1. Make a bot. Open Telegram, message @BotFather, send /newbot, and follow the prompts. It replies with a token that looks like 123456789:AAE….

2. Send your bot a message. Any message. A bot cannot start a conversation with you, so this step is what makes the next one possible.

3. Find your chat ID. This is the part that is not obvious. Open this URL in a browser, with your token in place of <TOKEN>:

https://api.telegram.org/bot<TOKEN>/getUpdates

Look for "chat":{"id":…} in the response. That number is your chat ID.

4. Enter both. Where depends on which you are setting up — the token goes to your OS keychain either way, and the chat ID is ordinary settings.

TokenChat ID and the switch
CLI/telegram — the input is masked/settings → Telegram chat ID, then Answer confirmations on Telegram
Mac appSettings → Tools & Privacy → Approval on your phoneSame panel, same section
One bot serves both. The same token and chat ID work in the terminal and in the app — there is no need for a second bot, and the message says which one is asking.

What it looks like

The message names the tool, shows the command, and offers three buttons — Run, Skip, Cancel — the same three the terminal and the desktop sheet offer. Answer there instead and the message updates to say where it was decided, rather than leaving buttons that no longer do anything.

There is deliberately no Always allow on the phone. A standing permission is a decision that belongs at the keyboard, where you can see what you are granting it to.

Only your chat ID can answer. A bot's username is discoverable, so every incoming answer is checked against the ID you entered. Without that check, anyone who found your bot could approve a destructive command on your Mac. Do not share the token — it is the bot.
Telegram sees the command. The message has to contain it, or you cannot judge what you are approving. If that is not acceptable for your work, leave this off — the desktop confirmation is unchanged and still authoritative.

If it does not arrive

SymptomUsually
Nothing ever arrivesThe switch is off, or the chat ID is empty
"chat not found"The ID belongs to a chat with a different bot — or you have not messaged this one yet
Worked once, then stoppedToken was regenerated in BotFather — enter the new one
Buttons do nothingThe question was already answered on the Mac
getUpdates returns an empty resultYou have not messaged the bot yet — do step 2