Skip to content

Trust

"bash": "ask" means approving git status for the hundredth time. OpenCode’s own “Always” is broader than it looks: it approves by prefix, and its prefixes count flags as words, so approving docker compose -p cockpit up -d also approves docker compose -p prod down -v. Trust sits between: you approve, it counts, and once you have approved the exact same command enough times in a row it answers for you — and records it, every time.

Terminal window
opencode plugin @opencode-cockpit/[email protected] --global --force # OpenCode 1
opencode plugin add @opencode-cockpit/[email protected] # OpenCode 2

Or through the bundle, where it is on by default (features.trust: false turns it off).

It only acts where OpenCode asks you — a "bash": "ask", or "permission": "ask", in opencode.json. Where your config says allow or deny, OpenCode never asks, and Trust never sees the request.

the same as git status?
git statusyes
git status (spacing), git 'status' (quoting)yes
git status -sno — another argument
cd web && git statusno — another directory
git status run by the general agentyes — trust is the project’s, whichever agent asks

A line with several commands counts for each, and is answered only when every one is trusted or allowed by your config: git status && rm -rf build waits for you even when git status is trusted. Anything that cannot be read for certain is always asked and never counted: $(…), backticks, $VAR, eval, sh -c, a pipe into a shell, a heredoc.

An approval by any agent counts towards the one rule: you give permission for the work in a project, not for one agent in it. The ledger still records which agent asked.

Every permission OpenCode 1.18.33’s tools ask, and what Trust counts each by. They only reach Trust when your config sets them to ask.

Permission (tool)Counted byFamilyLearned by itselfSuggested
bashthe command, exactlyprogram and subcommand (git status)its plain readsyes
edit (edit, write, apply_patch)the fileits folder (src/)noyes
readthe fileits folder (read src/)yesyes
glob, grepthe patternthe whole tool (any grep)yesyes
websearchthe querythe whole toolno — a query leaves the machineyes
webfetchthe host—nono
todowrite, lspone rule—nono
skill, taskthe skill, the subagent type—nono
external_directorythe folderthe folder; the temp dir as one ($TMPDIR)noyes — and only a widened folder answers it
doom_loopnever answered

A read of a file that may hold secrets (.env, a key, ~/.ssh) is OpenCode’s own default ask, and stays yours to answer however often you approve it.

Before a tool reads or writes outside the project, OpenCode asks external_directory for the folder. Trust counts those approvals by folder, like any other — and never answers one by its count: approving a folder three times still leaves the next one to you. What answers it is a folder you widened with w, and it covers the folders under it, never the ones beside it.

Most of those prompts are the agent’s scratch space in the OS temp dir (/var/folders/…/T/opencode/… on macOS, /tmp/… elsewhere), so the temp dir is one family, $TMPDIR, whatever name a tool gives it (/var/… or /private/var/…, /tmp or /private/tmp). Once your approvals across two or more of its folders reach the threshold, the ledger suggests it:

★ any folder in $TMPDIR? 14 in 6 │ outside $TMPDIR/ family of 6 folders
│ ★ Suggested · approved 14× across 6 folders in your temp directory
│ Widen [w] lets Trust answer reads and writes there from now on —
│ any folder in $TMPDIR; a folder that may hold secrets still asks.

Never suggested and never widened, however often you approve them: /, your home folder and the folders above it, ~/.ssh, ~/.aws, ~/.gnupg, ~/.config, ~/.kube, ~/.docker, /etc, any folder that may hold secrets, and a folder that holds the project — widening it would answer for every project beside this one. A request naming one of them is always yours, and is not counted.

  • Three approvals in a row (threshold) and it is trusted — or, for a command that only reads, its whole family is (below).
  • A reject resets the count to nothing.
  • Dangerous commands cost more — threshold + dangerExtra, eight by default: rm, rmdir, dd, kill, chmod -R, git push, git reset --hard, git clean, git checkout -- …, git branch -D, docker rm/rmi/prune, docker compose down -v, kubectl delete, terraform apply/destroy, DROP/TRUNCATE in a SQL client, npm publish, --force, and anything under sudo. env, time, nohup, timeout and xargs are looked through.
  • Unused for 30 days (expireDays), it has to be earned again.
  • Only your approvals count. Trust’s own answers do not, and nor does any reply faster than 300ms — nobody reads a prompt that fast; OpenCode’s --auto answers in about 20ms.

An exact rule rarely repeats in real work: head -3 on a new file, rg for a new word, one new command in a line of five, and the whole line asks again. So a command that only reads counts twice — for itself, and for its family. Three approved heads on three files, and any head that only reads is answered, by a family Trust learned. glob, grep and read (by folder) are learned the same way. The ledger marks the family reads and its commands ✓ read.

What reads: ls, cat, head, tail, wc, grep, rg, fd, echo, jq, sort, cut, diff, stat, eza and the like; a sed whose script only prints (sed -n 1,80p, s/a/b/g — never w, e or -i); a git that only looks (status, log, diff, show, blame, branch --show-current, stash list, worktree list, remote -v, config --get …). It is an allowlist: a program not on it is not a read, and keeps earning trust one command at a time.

A learned family covers plain reads only — never:

  • a command with an env var or a wrapper in front (LD_PRELOAD=… ls, sudo cat, xargs head);
  • one that writes: a redirection (ls > out.txt) or a flag (sort -o, tree -o, uniq a b);
  • one that runs something: rg --pre, fd -x, git --ext-diff;
  • anything dangerous;
  • a file that may hold secrets: .env, *.pem, *.key, id_rsa, ~/.ssh, ~/.aws, credentials — cat README.md is in a learned cat, cat .env asks.

A reject of a read in the family starts it over; a reject of something it never covers (head .env) does not. Unused for expireDays, it is gone. w on it — “Forget head reads” — takes it back, until enough reads in a row teach it again. "learnReads": false turns it off: then only the families you widen answer.

awk, find, less and xargs are not reads: their scripts and actions can write or run.

A command is written to the ledger, shown on screen and logged. A secret in it is replaced first, with a keyed hash: GITHUB_TOKEN=‹#3fa9c2› gh api …. Masked:

  • an env value under a name that says secret (TOKEN, KEY, SECRET, PASSWORD, AUTH …), or that looks generated, or is long;
  • the value of --token, --password, --api-key and the like, and of export NAME=…;
  • Authorization:, Cookie: and X-Api-Key: headers, Bearer …, and the password in postgres://user:pass@host;
  • tokens by their shape, wherever they are: sk-…, ghp_…, github_pat_…, xoxb-…, AKIA…, shpat_…, a JWT.

Two different tokens stay two commands: trust earned with one is not trust for another. A masked value keeps the environment it names — DATABASE_URL=‹#a1b2c3 prod› — so a production URL still costs more and never shares a family with dev. NODE_ENV=production and PORT=3000 stay as they are. The key is per project, in mask.key beside the ledger, readable by you alone, so a short password cannot be found by hashing guesses. OpenCode’s own “always” patterns are masked too.

The ledger is only ever appended: lines written before 0.11 keep what they held. Delete them from events.ndjson by hand if you want them gone.

A specific pattern set to ask — "git push *": "ask" — is you asking to be asked, and Trust never answers a request it matches. A catch-all — "bash": "ask", { "*": "ask" }, or no rule at all — is a default, and that is the gap Trust fills. Rules are read from OpenCode itself (the merged config, the agent’s own rules last), matched with OpenCode’s wildcards, last match wins.

Trust never writes opencode.json. The ledger copies a rule for you to paste.

The block is hidden by default: the sidebar already carries the statusline, subagents and shells, and Trust answers exactly the same without it — /trust shows what it did. To show it:

~/.config/opencode-cockpit/config.json (or a project's .cockpit.json)
{ "trust": { "sidebar": true } }

“Show or hide Trust in the sidebar” in the command palette flips it for this session; it is not remembered. Hidden or not, a failure — a ledger that could not be saved — always shows there.

Trust 4 auto
● git status 7×
● edit src/app.ts 3×
○ docker compose -p cockpit… 2/3

A filled dot is something Trust answered in this window, with how many times it has answered it in all; a hollow one is the request on screen now, and how far it is from being trusted. Nothing answered and nothing counting, no block; paused, it says so; a failure always speaks. Every answer is also a line in ~/.cache/opencode-cockpit/cockpit.log. No toasts.

OpenCode’s prompt is drawn for one frame (about 18ms) before Trust’s answer removes it: a plugin cannot get in before OpenCode’s own handler.

/trust, ctrl+x p, or “Trust” in the palette opens on the ledger, with today’s answers as a strip above it; a opens them in full — what Trust did for you, newest first, and what it is about to do:

Trust · app activity ● answering
TODAY Trust answered 5 prompts for you ▂▁▅▂█▄█ last 7 days
▌✓ 20:31 git status --short build trusted since Sep 16, 3 in a row
✓ 20:30 ls -la general trusted since Sep 16, 3 in a row
✓ 20:21 bun test build trusted since Sep 16, 3 in a row
✓ 20:13 git status --short && echo tr… build both commands trusted
✓ 19:43 cat src/app.ts general in a family you widened: any cat …
ALMOST THERE closest first
○ bun --version ▰▰▱ 2 of 3
○ git push origin feat/trust ▰▰▰▰▰▱▱▱ 5 of 8 dangerous
! WATCH OUT OpenCode's own "always" approves more than it looks, until it restarts
! find . * sort -rn * general [enter] what it covers
RULES 8 trusted · 2 learning · 6 seen once l Open the ledger
[enter] Why [x] Revoke [w] Trust Family [a] Ledger [p] Pause [?] Keys [esc] Back
  • Today lists every answer Trust gave in this project, from any window: when, the agent it answered, and why in a few words — the approvals that earned it and when, both commands of a line trusted, the family you widened, or the reads it learned. One line answered again and again is one row with a count. With nothing today, the latest answers from earlier days, with their day. The sparkline is answers per day for the last week, each scaled to the busiest.
  • Almost there is what is still learning, closest first and a dangerous command last: a meter of the approvals in a row that count (▰) and those still to go (▱), red and longer for a dangerous command.
  • Watch out appears only when you gave OpenCode its own “always”: it approves every command that starts that way, until OpenCode restarts, and Trust cannot take it back.
  • Rules counts the project: trusted, learning, and seen once — the commands approved a single time that mostly never come back.

enter on any row shows why: that rule’s card in the ledger. a, l, or a click on the button, goes back to the ledger.

Every rule, as a tree of families, with a card for the one selected — always on screen, so there is no details key to find:

Trust · app 8 trusted · 2 learning · 6 seen once ● answering
Today ✓ 5 answered · last 20:31 git status --short [a] Activity
────────────────────────────────────────┬───────────────────────────────────────────────────────────
COMMANDS / filter │ cat src/app.ts
▸ git ! 1 ✓ 1 ▰ │ ✓ Answered · through any cat …
▸ ls reads 1 ✓ │
bun test ✓ trusted │ Exactly cat src/app.ts
▸ echo reads 1 ✓ │ Still asks dangerous ones, and any that write a file or
▾ cat any 2 ✓ │ run another program.
▌ cat src/app.ts ✓ any │ History answered 1×
cat package.json ✓ any │ answered through any cat …, not by its own
▸ head reads 1 ✓ │ count · last asked by general
bun --version ▰▰▱ 2/3 │ Family cat · 2 commands, 2 trusted
▸ seen once pwd sed … 6 ○ │ any cat … trusted 7d ago · [w] undoes it
│
EDITS │
▸ src/ 1 file 1 ✓ │
│
! OpenCode always 2 broad rules │
│ x Revoke w Undo any cat c Copy rule
────────────────────────────────────────┴───────────────────────────────────────────────────────────
[↑/↓] Move [←/→] Fold [tab] Card [/] Filter [a] Activity [?] Keys … [esc] Close

The card holds the command whole, on a raised panel; how it stands; the exact text and what still asks; the history that earned it — each approval with how long ago, → trusted where the streak reached the threshold, then how often Trust answered it, and which agent asked it last; its family and what w would do; when it expires. Its buttons can be clicked, or reached with tab. Below 90 columns the card moves under the tree, with the selection kept in view above it. / filters the tree by text.

Rules are grouped by what they do. A family is the program — ls, echo — or, for a tool with subcommands, the program and its subcommand: git status, docker compose up, kubectl get, terraform plan. A package manager’s run keeps its script: npm run test and npm run deploy are two families.

CommandFamily
ls -la, ls -x, ls -R docsls
git -C /x status --shortgit status
docker compose -p prod down -vdocker compose down
npm run testnpm run test
sudo lssudo ls — running as root is not ls
cd web && bun testbun test in web
NODE_ENV=prod npm run buildNODE_ENV=… npm run build
ls > out.txtls
edit src/app.tsedit src/
read src/app.tsread src/
grep createServer, glob **/*.tsgrep, glob — the tool

A command the agent ran from another folder — cd packages/api && npx vitest run, or the tool’s own working directory — is its own rule and its own family: the same words do something else there. The ledger shows the command first and the place after it, muted and short — npx vitest run in packages/api, python3 upload.py in ~/…/support-data-uploader/scripts — so a narrow row cuts the command’s tail, never its program. The card says it once in words: “Run from packages/api — the agent changed folder first.”

A family with one command is drawn as that row; the others start folded, with how many of their commands are trusted (✓) and how many are not yet (○). space, enter or → opens one, listing its first three commands and + N more; ← folds it, or from a command inside it goes to its heading. A command appears once, however many agents asked it: approvals by build and by general add up to one rule, the project’s.

The card shows every argument quoted where a font could merge it: a programming font draws --- as one line, so echo --- is shown echo "---"; so are ->, ==, !=, <=, >=, www and any word that is only punctuation. Quotes are not enough on their own — a ligature font still merges --- inside them — so an argument that is only punctuation is also said in words: “"---" is 3 hyphens”, “hyphen, greater-than” for ->.

w trusts any command in the family, in this project. It is the one way trust gets wider than what you approved, other than the reads Trust learns, and only you do it — it is written to the ledger like everything else. From then on any ls … is answered, except:

  • a dangerous command — docker compose down -v is not covered by docker compose down;
  • one that writes a file through a redirection — ls > out.txt (2>/dev/null and 2>&1 write nothing, and are covered) — or through a flag: sed -i, perl -i, awk -i inplace, sort -o, tee file, curl -o, wget, tar x, unzip, find -delete;
  • one that runs another program — find -exec, git -c …, rg --pre, fd -x;
  • a line that cannot be read, and anything a specific ask in your config matches.

A dangerous family — git push, rm, kubectl delete, sudo … — can never be widened: w says why and does nothing. w again, or x on the family, goes back to exact rules. An answer through a widened family says so: ✓ widened in the ledger, “in a family you widened” in the activity, ● ls -x · any ls in the sidebar, the family in the log. Widening does not expire; undo it when you no longer want it.

Everything that is not a read widens only when you press w — and the ledger says when it is worth it. When approvals across two or more commands of a family reach the threshold, the family is listed under SUGGESTED at the top of the ledger:

Trust · app 13 trusted · 1 learning · 5 seen once ● answering
Today ✓ 4 answered · last 15:54 head -60 src/routes.ts [a] Activity
────────────────────────────────────────┬───────────────────────────────────────────────────────────
SUGGESTED / filter │ mcpx db-local execute_sql family of 3 commands
▌★ any mcpx db-local…? 3 in 3 │ ★ Suggested · 3 approvals in a row across 3 commands
│
COMMANDS │ Widen [w] trusts any mcpx db-local execute_sql … —
▸ head reads 5 ✓ 1 ○ │ except dangerous ones, ones that write a file
▸ sed reads 4 ✓ 1 ○ │ and ones that run another program. [d] stops
▸ rg reads 4 ✓ │ suggesting it.
mcpx db-prod execu… ! ▰▰▱▱▱▱▱▱ 2/8 │ Today 3 approvals of yours today were in it — [w] …
▸ mcpx db-local exec… 3 ○ │ Commands mcpx db-local execute_sql --sql "se… ○ once
│
│ w Trust any mcpx db-local execu… d Dismiss
────────────────────────────────────────┴───────────────────────────────────────────────────────────
[↑/↓] Move [←/→] Fold [tab] Card [/] Filter [a] Activity [?] Keys … [esc] Close

Its card says how many approvals in a row across how many commands, and how many of today’s approvals were in it. w widens it, with the same exceptions as any widening; d dismisses it, for good. A dangerous family — mcpx db-prod …, git push — is never suggested, and a widening you undo is not suggested again. A suggestion never widens anything by itself.

On the activity:

Key
j k ↑ ↓, wheel, clickMove over answers, what is close, and OpenCode’s own approvals
enterWhy: the rule’s card in the ledger
xRevoke what answered (the rule, or the widening that answered it); forget a count still learning
wTrust any command in the family, or undo it
cCopy it as an opencode.json rule, to paste yourself
a, l, click on the buttonBack to the ledger
/Open the ledger and filter it
pPause Trust in this project — it keeps counting and answers nothing; again to resume
?Every key, one line each
escClose (q too)

In the ledger:

Key
j k ↑ ↓, wheel, clickMove over families and commands
← h / → lFold / open a family — on a command inside one, go to its heading
space enterOpen or fold a family; on + N more, list the rest
tabInto the card’s buttons and back; ← → choose one, enter presses it
xRevoke a command; on a family, every command in it and its widening. Still learning, it forgets the count
wTrust any command in the family, or undo it; on a learned family, forget it
dDismiss a suggestion: it is not suggested again in this project
cCopy it as an opencode.json rule — a family as "ls *": "allow"
/Filter by text; enter keeps it, esc clears it
pPause or resume
?Every key, one line each
escOne step back: the keys, the card’s buttons, the filter — then to the activity, which esc closes

In the bundle’s entry ("trust": { … }), the package’s own, or the trust section of ~/.config/opencode-cockpit/config.json and a project’s .cockpit.json:

SettingDefault
threshold3Approvals in a row, by you, before Trust answers
dangerExtra5What a dangerous command costs on top
expireDays30Days unused before trust has to be earned again; 0 never
learnReadstrueLearn a family of plain reads by itself (head, rg, git status …); false leaves families to w
enabledtruefalse turns Trust off
sidebarfalseShow the block in the sidebar. The palette’s “Show or hide Trust in the sidebar” flips it for the session
sidebarRows3Answers listed in the sidebar
keybinds{ "cockpit.trust.ledger": "<leader>p" }The key that opens the ledger

Where the block sits is the top-level "sidebar" list’s to say — Trust last by default; a trust.sidebarOrder from before 0.9 is not read. See Configuration.

~/.local/share/opencode-cockpit/trust/<project>-<hash>/events.ndjson — outside the project, so it never turns up in git status or travels to anyone who clones the repository. One line per event (asked, approved by you, rejected, answered by Trust, revoked, widened, dismissed, paused), only ever appended, and shared by every OpenCode window on the project. $COCKPIT_HOME or $XDG_DATA_HOME move it. Beside it, mask.key is the key secrets are hashed with; it is never in the ledger.

Terminal window
bunx @opencode-cockpit/trust preview

Draws the sidebar block, the activity and the ledger from sample projects — a busy week, a new project, a paused one, dangerous commands on their way, a widened family, reads learned and a family suggested (--sample learning) — in your terminal. --view activity or --view ledger draws one screen, --columns and --rows another size, and --html a page to judge the colours in a browser.