Summary
The Gmail no-send safety system has three layers: the --gmail-no-send flag, the global gmail_no_send config key, and per-account guards (gog config no-send set <email>). The first two are enforced pre-Run() in enforceGmailNoSend and block even under --dry-run. The per-account guard is only checked post-auth inside each send command's Run() — which --dry-run exits before reaching. Result: --dry-run reports would gmail.send for an account whose sends are hard-blocked.
Real (non-dry-run) sends are still blocked by checkAccountNoSend before any API call, so no mail can actually leak. The failure is the safety signal, not the send itself. But that signal is exactly what agent operators use to verify their guardrails: probing with --dry-run (the safe way to test) tells you the guard is broken when it isn't — or, worse, an agent told to verify its constraints concludes sending is permitted. I hit this today: a --dry-run probe of a per-account guard sailed through, and I concluded the guard didn't work and switched to the global key instead.
Steps to reproduce
On v0.34.0, with an isolated config:
Expected
Gmail sending is blocked for [email protected] (config no-send) — matching the behavior of the other two layers, which both block under --dry-run:
$ gog gmail send ... --gmail-no-send --dry-run
Gmail sending is blocked by --gmail-no-send
$ gog config set gmail_no_send true && gog gmail send ... --dry-run
Gmail sending is blocked by config gmail_no_send
Actual
Dry run: would gmail.send
{
"attachments": [],
...
Same for all five send paths (gmail send, gmail forward, gmail reply/reply-all, gmail autoreply, gmail drafts send).
Root cause
dryRunExit runs before requireGmailSendService in every send command, and requireGmailSendService is the only place checkAccountNoSend fires. In internal/cmd/gmail_send.go on current main: dryRunExit at L120, requireGmailSendService at L142. enforceGmailNoSend (internal/cmd/gmail_no_send.go) checks the flag and the global key pre-Run() but never the per-account map.
The ordering gap traces back to the original per-account design in #454 (mine) — the landed implementation preserved it faithfully.
Proposed fix
Enforce the per-account guard in enforceGmailNoSend alongside its two siblings. The account is resolvable there without auth via requireAccount(flags) (flag → GOG_ACCOUNT → configured default, with alias resolution from config). When no account resolves (e.g. dry-run with no configuration), skip — the command owns that failure mode, and the existing post-auth checkAccountNoSend still covers real sends as defense-in-depth.
PR with fix + tests incoming.
Summary
The Gmail no-send safety system has three layers: the
--gmail-no-sendflag, the globalgmail_no_sendconfig key, and per-account guards (gog config no-send set <email>). The first two are enforced pre-Run()inenforceGmailNoSendand block even under--dry-run. The per-account guard is only checked post-auth inside each send command'sRun()— which--dry-runexits before reaching. Result:--dry-runreportswould gmail.sendfor an account whose sends are hard-blocked.Real (non-dry-run) sends are still blocked by
checkAccountNoSendbefore any API call, so no mail can actually leak. The failure is the safety signal, not the send itself. But that signal is exactly what agent operators use to verify their guardrails: probing with--dry-run(the safe way to test) tells you the guard is broken when it isn't — or, worse, an agent told to verify its constraints concludes sending is permitted. I hit this today: a--dry-runprobe of a per-account guard sailed through, and I concluded the guard didn't work and switched to the global key instead.Steps to reproduce
On v0.34.0, with an isolated config:
Expected
Gmail sending is blocked for [email protected] (config no-send)— matching the behavior of the other two layers, which both block under--dry-run:Actual
Same for all five send paths (
gmail send,gmail forward,gmail reply/reply-all,gmail autoreply,gmail drafts send).Root cause
dryRunExitruns beforerequireGmailSendServicein every send command, andrequireGmailSendServiceis the only placecheckAccountNoSendfires. Ininternal/cmd/gmail_send.goon current main:dryRunExitat L120,requireGmailSendServiceat L142.enforceGmailNoSend(internal/cmd/gmail_no_send.go) checks the flag and the global key pre-Run()but never the per-account map.The ordering gap traces back to the original per-account design in #454 (mine) — the landed implementation preserved it faithfully.
Proposed fix
Enforce the per-account guard in
enforceGmailNoSendalongside its two siblings. The account is resolvable there without auth viarequireAccount(flags)(flag →GOG_ACCOUNT→ configured default, with alias resolution from config). When no account resolves (e.g. dry-run with no configuration), skip — the command owns that failure mode, and the existing post-authcheckAccountNoSendstill covers real sends as defense-in-depth.PR with fix + tests incoming.