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:
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
| Recorded | Not recorded |
|---|---|
File paths, commands, URLs | File contents, ever |
Reads, writes, searches, fetches | Anything from a project you did not run in |
Tool calls the boundary refused | Your 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.
.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 it | Turning it off | |
|---|---|---|
CLI | /audit | /audit off |
Mac app | Open the project in the CLI, or read .codeep/audit/ directly | Settings → Tools & Privacy → Audit record |
VS Code | Codeep: Show Audit Record | Uses 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.
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>/getUpdatesLook 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.
| Token | Chat ID and the switch | |
|---|---|---|
CLI | /telegram — the input is masked | /settings → Telegram chat ID, then Answer confirmations on Telegram |
Mac app | Settings → Tools & Privacy → Approval on your phone | Same panel, same section |
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.
If it does not arrive
| Symptom | Usually |
|---|---|
Nothing ever arrives | The 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 stopped | Token was regenerated in BotFather — enter the new one |
Buttons do nothing | The question was already answered on the Mac |
getUpdates returns an empty result | You have not messaged the bot yet — do step 2 |
Starting a task from your phone
The section above lets your phone answer a question the agent chose to ask. This lets it ask one. Send the bot an instruction in plain words and it runs, as though you had typed it into the terminal — and the answer comes back to the same chat.
Turning it on
The token and chat ID are the ones you already entered above; there is no second bot and no second setup. In the CLI, /settings → Start tasks from Telegram, then restart the CLI. In the Mac app, Settings → Telegram → Start tasks from Telegram, which takes effect straight away. Then message the bot.
Which conversation it drives
In the Mac app, right-click a conversation in the sidebar and choose Drive this from Telegram. A paper plane marks it, and every instruction from your phone lands there no matter what you are looking at — so you can read one conversation while the phone works in another. Choose it again to release it.
With nothing chosen, instructions go to the selected conversation. That keeps the feature usable before you have picked one, but it also means the target moves when you switch conversations at the Mac. Pick one for anything you plan to leave running.
While a run is in flight
An instruction sent while the agent is busy waits, and the bot says so. One waits at a time: send a second and it replaces the first, because two corrections typed during one long run mean the second, not both in order. The reply tells you when it displaced something rather than dropping it quietly.
An instruction waits behind a run you started at the machine too. The queue does not care where the current work came from — only that something is already using the agent.
What it will not run
| Sent | What happens |
|---|---|
A message from any other chat | Ignored, with no reply at all — answering confirms the bot is live and attached to something worth probing |
A photo, sticker or voice note | Refused: text only |
Anything starting with / | Refused — those are addressed to the bot, not to the agent |
More than 2000 characters | Refused: that is a paste, not an instruction |
A message older than five minutes | Refused, and it says so |
The answer comes back
A run started from your phone sends its answer to your phone, because "finished, 33K tokens" answers nothing you asked. A run you started at the machine does not, by default: the answer is already on the screen you are sitting at, and sending it would put file contents or a command line into a chat for nothing. If you start something and walk away, turn on Send terminal answers to Telegram in the CLI's /settings, and the "finished" message carries the answer too (CLI 3.8.0 and later). In the Mac app (3.8.1 and later) it is Send answers to Telegram under Settings → Tools & Privacy → Approval on your phone: a run you started at the Mac that takes a minute or longer sends how it ended and the answer. Long answers are cut, with a line saying they were, and markdown is stripped — an answer should read as words on a phone, not as **words**.