#auto-approvals not working `exec-approvals.json`

1 messages · Page 1 of 1 (latest)

finite muskBOT
#

To help others find answers, you can mark your question as solved via Right click solution message -> Apps -> âś… Mark Solution

deep thistleBOT
#

Two big things jump out from your exec-approvals.json:

  1. It’s not valid JSON right now (so it may not be loading at all).
    In your paste there’s a broken entry around op item get (missing fields / braces) and then another { starts immediately after. If the gateway/node can’t parse the file, it’ll fall back to defaults and you’ll keep getting prompts.

Quick sanity check on the host that’s executing exec:

openclaw --version
python3 -m json.tool ~/.openclaw/exec-approvals.json >/dev/null && echo OK

If that JSON check fails, fix the file (or edit via UI / CLI), then restart the gateway.

  1. Your allowlist patterns won’t match, because allowlists match resolved binary paths, not command text.
    From the docs: “Manual allowlist enforcement matches resolved binary paths only (no basename matches). Patterns should resolve to binary paths (basename-only entries are ignored).”
    So entries like:
  • "op item create"
  • "op read"
#
  • "export OP_SERVICE_ACCOUNT_TOKEN"
  • "~/.openclaw/workspace/.secrets/1password.env" (not an executable)

…won’t ever allow anything. You need to allowlist the actual op executable path, e.g.:

which op
# then allowlist that path, e.g. /home/openclaw/.local/bin/op

Also: if the agent runs compound shell commands (e.g. curl ... && unzip ... && mv ...), every segment’s executable must be allowlisted (or it’ll still prompt). That’s why “I allowlisted curl but it still prompts” is common.

What I’d do next (fastest path)

  1. Confirm where exec is running (Gateway host vs Node host). Approvals are per execution host. If you’re executing on a node, editing the gateway’s approvals file won’t help.
  2. Validate + load-check:
    openclaw approvals get --gateway
    
#

(This confirms what the gateway thinks the approvals policy is, instead of guessing based on the file.)
3. Add the real binary path(s) via CLI (less error-prone than hand-editing JSON):

openclaw approvals allowlist add --gateway --agent main "$(which op)"

About “approval pop-up sometimes doesn’t show in Control UI”

That usually means your operator client/UI isn’t connected when the gateway broadcasts the approval request. A reliable workaround is to forward approvals to chat and approve with /approve (docs show the approvals.exec config block):
https://docs.openclaw.ai/tools/exec-approvals (section “Approval forwarding to chat channels”)

Docs references:

If you paste:

  • openclaw --version
#
  • openclaw approvals get --gateway
  • and (if you’re using a node) openclaw approvals get --node <id>
    …I can tell you exactly which host/file is being used and why the allowlist isn’t matching.
deep thistleBOT
#

No — don’t include args in the allowlist pattern.

Exec approvals allowlists match the resolved executable path only (the “binary”), not the full command line. So you want an entry like:

  • /home/openclaw/.local/bin/op (whatever which op prints)

…and then op item create, op item get, op read, etc will all be allowed because they all resolve to the same op binary.

What to do

On the machine where the command is actually executing (gateway vs node):

which op
openclaw approvals allowlist add --gateway --agent main "$(which op)"
openclaw approvals get --gateway
#

(If you’re executing on a node, use --node <id|name|ip> instead of --gateway.)

Why you still got prompted a lot

  • Entries like "op item create" won’t ever match (not a path).
  • Entries like "export OP_SERVICE_ACCOUNT_TOKEN" also won’t match (that’s a shell builtin, not a binary).
  • If you run a chained install command (curl ... && unzip ... && mv ... && chmod ...), you must allowlist each executable used (/usr/bin/curl, /usr/bin/unzip, /usr/bin/mv, /usr/bin/chmod, etc). Otherwise it’s still an allowlist miss and it will prompt.

Docs: https://docs.openclaw.ai/tools/exec-approvals (see “Allowlist (per agent)”) and https://docs.openclaw.ai/cli/approvals

If you paste openclaw approvals get --gateway (and --node ... if applicable) I can tell you immediately whether you’re editing the right host + whether the gateway is actually loading your policy.

#

For those exact commands (source … && export … && op …), there isn’t a “perfect” allowlist that only allows that and nothing else, because:

  • source and export are shell builtins (not binaries), so they can’t match a “resolved executable path”.
  • The presence of && means the exec layer treats it as shell text, and on allowlist mode that’s typically an allowlist miss unless the shell itself is allowlisted.

So you have two practical options:

Option A (recommended): change the command shape so only op runs

If you make the agent run only op … (no source, no export, no &&), then your allowlist can simply include the op binary path:

  1. Find the actual op path:
which op
# e.g. /home/openclaw/.local/bin/op
  1. Allowlist it:
#
openclaw approvals allowlist add --gateway --agent main "$(which op)"
  1. Then ensure OP_SERVICE_ACCOUNT_TOKEN is already in the environment (so the agent doesn’t need source/export). On a VPS this usually means adding it to the gateway service environment (systemd / the DO image’s service config), so every exec host=gateway sees it.

Result: op item create/list/get/read … should stop prompting, because it’s always the same executable.

Option B (works, but is basically “no approvals for shell”): allowlist the shell too

If you insist on keeping source … && export … && op … as one chained command, you’ll typically need to allowlist the shell executable that runs it (often /usr/bin/bash or /bin/bash, sometimes /usr/bin/sh).

Example allowlist entries you’d add (patterns are for binaries only):

  • /usr/bin/bash (or /bin/bash / /usr/bin/sh — whichever your approvals dialog shows as the resolved executable)
  • /home/openclaw/.local/bin/op

CLI way:

#
openclaw approvals allowlist add --gateway --agent main /usr/bin/bash
openclaw approvals allowlist add --gateway --agent main "$(which op)"

Important caveat: once bash is allowlisted, the agent can run essentially any shell command without prompts (because it’s all “bash running some text”). If your goal is “no approvals ever”, that’s consistent — but it’s a big trust decision.


One more correction based on your JSON

Entries like these will never help:

  • op item create / op read (not a path)
  • export OP_SERVICE_ACCOUNT_TOKEN (builtin)
  • ~/.openclaw/workspace/.secrets/1password.env (not an executable)

They’re ignored for allowlisting purposes.

#

If you paste the resolved executable path shown in the approval request (or the relevant gateway log line for the denied exec), I can tell you exactly whether you need to allowlist /usr/bin/bash vs /usr/bin/sh, and whether you’re editing the right host (gateway vs node).

#

Yep — avoiding allowlisting the whole shell is the right instinct.

1) Where to store OP_SERVICE_ACCOUNT_TOKEN (so you can stop doing source … && export …)

Best practice on a VPS: put it in the Gateway’s env, so the agent can run plain op … without any source/export/&& at all.

OpenClaw loads env vars from ~/.openclaw/.env (non-overriding) and the process environment. Docs: https://docs.openclaw.ai/help/environment

So on your DO box (as the user that runs the gateway — likely openclaw):

~/.openclaw/.env

OP_SERVICE_ACCOUNT_TOKEN=op_service_account_token_goes_here

Then restart the gateway so the daemon picks it up.

This is better than ~/.openclaw/workspace/.secrets/1password.env because it removes the need for shell builtins (source, export) entirely.

#

2) The allowlist you actually want (without allowlisting bash/sh)

Once the token is already in the gateway env, your commands become just:

  • op item create ...
  • op item list ...
  • op item get ...
  • op read ...

…and then the allowlist can be only the op binary path (no args):

  1. Find the resolved path:
which op
  1. Allowlist it for agent main on the gateway host:
#
openclaw approvals allowlist add --gateway --agent main "$(which op)"

That’s it. Do not add patterns like "op item create" — those won’t match.

If you want “never prompt me for allowlisted stuff”, make sure your approvals policy for main is:

  • security: "allowlist"
  • ask: "off" (or keep on-miss if you do want prompts for anything that misses)

Docs: https://docs.openclaw.ai/tools/exec-approvals

3) Are you on the wrong track with “agent-managed vault”?

Conceptually you’re on the right track, with two important safety constraints:

  • Treat that service account token as full access to whatever vault(s) you grant. So keep a dedicated vault for the agent, and don’t store anything there you wouldn’t be okay with the agent potentially leaking in a worst case (prompt injection/model mistake/etc).
  • Auto-creating accounts on websites can violate site ToS / trigger anti-abuse systems. A safer workflow is: agent does the research + drafts signup steps, and only automates credential storage + logins for sites you explicitly approve.
#

If you paste the output of:

openclaw approvals get --gateway
which op

…I can sanity-check that you’re editing the right host and that the allowlist will match the path OpenClaw is resolving.

deep thistleBOT
#

/opt/openclaw.env on the DigitalOcean 1‑click image is totally plausible — that file is typically wired into the systemd service as an EnvironmentFile=... (so the gateway daemon can read tokens even though it isn’t started from your interactive shell).

So: your setup is not necessarily wrong just because ~/.openclaw/.env doesn’t exist.

Why you still get approvals (even though op is allowlisted)

Your allowlist output already shows:

  • ~/.local/bin/op is allowlisted âś…

But the command you’re running is not “run op” — it’s a shell script line:

source ... && export ... && op ...

In allowlist mode, that usually resolves to the shell binary (bash/sh) as the executable, not op, so it misses (and prompts) unless you allowlist the shell (which you don’t want — fair).

Fix (no shell allowlisting): make OP_SERVICE_ACCOUNT_TOKEN part of the gateway’s environment

Then the agent can run only op ... and it’ll match the op allowlist entry.

#

Because your droplet already uses /opt/openclaw.env, the simplest approach is:

  1. Add this line to /opt/openclaw.env:
OP_SERVICE_ACCOUNT_TOKEN=...
  1. Restart the gateway service.

I don’t know the exact unit name on the DO image, so please run:

systemctl list-units | grep -i openclaw
systemctl cat <the-unit-name>

…and paste it. I’ll tell you exactly where it loads env from and which command to restart.

(Alternative that’s also supported by OpenClaw in general: ~/.openclaw/.env, docs here: https://docs.openclaw.ai/help/environment — but on your image /opt/openclaw.env is likely the “right” place.)

#

Then, change the agent’s exec usage to just:

  • op item create ...
  • op item list ...
  • op item get ...
  • op read ...

(no source, no export, no &&)

ToS question

Yes: I meant the target websites’ Terms of Service often forbid automated signup / bot-driven account creation. (Not OpenClaw’s ToS.) Even if it’s “your agent”, to them it still looks like automation.

If you paste systemctl cat ... for the gateway unit, we can lock in the correct env file + restart command for your specific droplet.

#
  1. “DO image” = DigitalOcean image (the 1‑click Marketplace droplet image you installed). Nothing more exotic than that.

  2. Your systemctl cat output confirms exactly what we needed: the Gateway service loads env from:

  • EnvironmentFile=/opt/openclaw.env
  • runs as User=openclaw

So if you want the agent to run plain op … (no source/export/&&) and have it “just work”, the cleanest approach on this droplet is:

A) Put the 1Password token into /opt/openclaw.env

Add:

OP_SERVICE_ACCOUNT_TOKEN=...

Then restart:

#
sudo systemctl restart openclaw.service
sudo systemctl status openclaw.service -n 50 --no-pager

Sanity check (without leaking the token):

sudo -u openclaw bash -lc 'test -n "$OP_SERVICE_ACCOUNT_TOKEN" && echo OP_TOKEN_PRESENT || echo OP_TOKEN_MISSING'

Docs background on env loading: https://docs.openclaw.ai/help/environment

B) Stop using shell builtins in the exec command

Right now you’re running:

source … && export … && op …

#

That forces the exec to be treated as “shell text”, which is why you keep getting approval prompts even though op is allowlisted.

Once the token is in the service env, have the agent run only:

  • op item create …
  • op item list …
  • op item get …
  • op read …

(no source, no export, no &&, no pipes/redirections)

C) Allowlist only the op binary (what you already basically did)

Your table already shows ~/.local/bin/op is allowlisted ✅. To be extra explicit, I’d add the fully-resolved absolute path too:

sudo -u openclaw bash -lc 'which op'
# then allowlist that path (gateway host, agent main)
#
openclaw approvals allowlist add --gateway --agent main "/home/openclaw/.local/bin/op"

(Allowlist matches the executable path, not op item create etc.)


“Auto-creating accounts on websites can violate ToS” — whose ToS?

Yes: the target website’s Terms of Service often prohibit automated signups / bot-like behavior. Just something to be aware of.

If after moving the token into /opt/openclaw.env you still see prompts, paste one approval request showing the resolved executable path (it’ll say what binary it thinks it’s running). That will immediately tell us what’s still forcing “shell mode.”

#

That check is misleading:

sudo -u openclaw bash -lc 'echo $OP_SERVICE_ACCOUNT_TOKEN' runs a fresh interactive shell as the openclaw user, and it will not inherit the systemd service environment from openclaw.service.

What we need to check is whether the running gateway process got the variable from EnvironmentFile=/opt/openclaw.env.

1) Verify the gateway service process has the env var

Run:

pid="$(systemctl show -p MainPID --value openclaw.service)"
echo "MainPID=$pid"
sudo tr '\0' '\n' < "/proc/$pid/environ" | grep '^OP_SERVICE_ACCOUNT_TOKEN=' && echo PRESENT || echo MISSING
  • If it prints PRESENT, you’re good: the gateway has the token, and then you can/should run plain op ... (no source / no export / no &&).
  • If it prints MISSING, systemd didn’t load it from /opt/openclaw.env.
#

2) If it’s missing: likely /opt/openclaw.env line format issue

In a systemd EnvironmentFile, the line must be exactly KEY=VALUE (no export).

Good examples:

OP_SERVICE_ACCOUNT_TOKEN=ops_xxx...
# or, if it contains special chars:
OP_SERVICE_ACCOUNT_TOKEN="ops_xxx..."

Bad examples:

export OP_SERVICE_ACCOUNT_TOKEN=...
OP_SERVICE_ACCOUNT_TOKEN = ...

Also check for systemd warnings about the env file:

#
sudo journalctl -u openclaw.service -b --no-pager | tail -n 80

(You don’t need to paste the token; just any warnings about EnvironmentFile=.)

After fixing /opt/openclaw.env, do:

sudo systemctl restart openclaw.service

…and re-run the /proc/$pid/environ check above.

3) Once the token is in the service env, your approvals problem goes away

Because you can stop running:

source … && export … && op …

#

…and instead run just:

op item create ... / op item list ... / etc

Since ~/.local/bin/op is already in your allowlist, those should no longer prompt.

If you paste the output of step (1) (PRESENT vs MISSING) + any warnings from journalctl, I’ll tell you exactly which case you’re in.

#
  1. Yep — good catch. Don’t paste that output anywhere (Discord/Telegram/etc). The command I gave prints the full OP_SERVICE_ACCOUNT_TOKEN=... line if it’s present.

Use a “presence check” that doesn’t echo the value:

pid="$(systemctl show -p MainPID --value openclaw.service)"
sudo tr '\0' '\n' < "/proc/$pid/environ" \
  | grep -q '^OP_SERVICE_ACCOUNT_TOKEN=' \
  && echo PRESENT || echo MISSING

That only prints PRESENT/MISSING and won’t leak the token.

(And at this point you already know it’s present, so you can stop checking.)


#
  1. Here’s a copy/paste prompt to send your agent on Telegram to re-test without source / export / && (so it should match the op allowlist and avoid approvals):

Prompt to your agent:

Re-check 1Password CLI integration using only direct op commands (no source, no export, no shell chaining).

  1. Run op --version to confirm op works.
  2. List vaults with op vault list and confirm the vault named "Shared with Yug" exists (if not, ask me which vault to use).
  3. Create a test item in that vault:
op item create --vault "Shared with Yug" --category password --title "Test Entry - OpenClaw" username="openclaw-test-user" password="openclaw-test-pass"
  1. Immediately list items and show me the matching entry (title only is fine):
op item list --vault "Shared with Yug"
  1. Fetch the created item by title and confirm it exists (do not print any sensitive fields; just show id + title):
#
op item get --vault "Shared with Yug" "Test Entry - OpenClaw" --format json | jq -r '.id, .title'

If any command fails due to auth, report the exact error output.

Notes:

  • Step 5 uses jq. If jq isn’t installed/allowlisted, tell it to skip that part or use --format json without piping. (Pipes can trigger extra approval logic; simplest is no pipes.)

If you want an even safer version (no jq, no pipes at all), use this instead for step 5:

op item get --vault "Shared with Yug" "Test Entry - OpenClaw"

…and tell it “don’t print passwords / sensitive fields”.

When you run this test, if you still get an approval prompt, paste only the approval dialog’s “resolved executable path” (but not the token/command output).

#

That allowlist will “work”, but it’s way broader than you need for the 1Password goal and a couple entries are pretty concerning from a security standpoint:

What’s concerning (highest risk first)

  • /usr/bin/python3 — this is basically “run arbitrary code”. If the agent can run python3, it can do almost anything the openclaw user can do (read files, make network calls, spawn processes, etc.). I would not keep this allowlisted unless you explicitly want that.
  • /usr/bin/openclaw — this lets the agent run the OpenClaw CLI itself. Depending on permissions/config, that can be a lot of power (changing config, reading status/logs, etc.). Usually not needed for normal agent tasks.
  • /usr/bin/cat — enables reading arbitrary files on the gateway host (cat /etc/..., cat ~/.ssh/..., etc.). Again: not needed for 1Password operations.
  • /usr/bin/curl — broad network egress. Useful for installing things, but after setup it’s typically not needed and expands the blast radius if something goes wrong.

What looks fine / intended

  • op — yes, this is the one you actually want for your use case. I’d make it the only entry long-term.

Also: I’d prefer making the op entry an absolute path (no ~), e.g. /home/openclaw/.local/bin/op, just to avoid any path-expansion weirdness.

Recommended “tight” allowlist for your stated goal

Keep only:

  • /home/openclaw/.local/bin/op (or whatever which op resolves to for the gateway service user)
#

…and remove the rest unless you have a specific ongoing need.

If you want to remove entries via CLI:

openclaw approvals allowlist remove --gateway --agent main /usr/bin/python3
openclaw approvals allowlist remove --gateway --agent main /usr/bin/openclaw
openclaw approvals allowlist remove --gateway --agent main /usr/bin/cat
openclaw approvals allowlist remove --gateway --agent main /usr/bin/curl

After that, the agent should still be able to op item create/list/get/read without approvals (since op stays allowlisted), and everything else will correctly prompt/deny.

If you paste openclaw approvals get --gateway (redact token fields), I can sanity-check that security/ask are set to what you intend too.

#

1) Approval dialogs sometimes don’t show up in Control UI

That almost always boils down to: the gateway broadcasted exec.approval.requested, but you didn’t have a connected operator client that actually surfaced it (tab disconnected, browser throttled the background tab, UI connected to the wrong gateway URL/token, etc.).

Two pragmatic fixes:

A) Make approvals reliable by forwarding them to Telegram (recommended on a VPS).
Then you’ll always see the prompt in chat even if the dashboard is asleep/disconnected, and you can approve with /approve.

Docs + config example are here (scroll to “Approval forwarding to chat channels”):
https://docs.openclaw.ai/tools/exec-approvals

Config shape (put in ~/.openclaw/openclaw.json):

{
  approvals: {
#
    exec: {
      enabled: true,
      mode: "both",          // "session" | "targets" | "both"
      agentFilter: ["main"],
      targets: [
        { channel: "telegram", to: "tg:YOUR_TELEGRAM_ID" }
      ]
    }
  }
}

(Use your actual Telegram “to” value you already use for messages; if you’re unsure, paste your channels.telegram config block with secrets redacted.)

B) Verify the Control UI is actually connected/healthy when you expect prompts
On the VPS, run:

#
openclaw gateway status
openclaw logs --follow

Then trigger one approval and watch logs for exec.approval.requested and whether any operator clients are connected at that moment.

Also: update. You’re on 2026.2.12 and UI/reliability fixes land frequently; being a couple patch versions behind can absolutely show up as “sometimes UI misses things”. Updating docs: https://docs.openclaw.ai/install/updating


2) Agent responds too slowly (big delays)

The common causes on a DO 1‑click droplet are:

  1. Model/API latency or rate limiting (provider slow, 429s, retries)
  2. Gateway host resource pressure (small droplet → CPU pegged or swapping)
  3. Tool waits (exec approvals, long-running op calls, network calls)
#
  1. Gateway restarts / reconnect churn (causes gaps + missed UI events)

Fast triage to pinpoint which one it is:

A) Check gateway logs while you send one message from Telegram

openclaw logs --follow

Send a simple “ping” to the bot, and see if logs show:

  • long model request durations
  • retry/rate-limit messages
  • tool calls taking ages
  • reconnect loops

B) Check droplet health

#
uptime
free -h

If you see high load averages and low free RAM / swap usage climbing, that’s a smoking gun: the droplet is underpowered for fast agentic work.

C) Update from 2026.2.12
Even if it’s not the root cause, it removes a whole class of “weirdness”. Follow: https://docs.openclaw.ai/install/updating


If you paste a small snippet from openclaw logs covering one slow interaction (timestamps included, redact tokens), I can tell you whether it’s model latency vs host resources vs reconnect/approval delays.

#

That openclaw approvals get --gateway output is actually a really good sign:

  • Gateway is reading ~/.openclaw/exec-approvals.json
  • Agent main has exactly 1 allowlist entry (~/.local/bin/op)
  • So any time the agent runs op … directly, it should not require an approval dialog.

1) “Sometimes I don’t see approval dialogs” — that can be expected now

There are two different cases:

Case A (good): no dialog because it didn’t need approval

If the command is allowlisted (e.g. op item list …), then no approval prompt should appear anywhere. That’s the whole point of allowlisting.

Case B (bad): dialog was required but you didn’t see it

That only happens when the command was an allowlist miss (or ask=always) and your operator client didn’t surface it (Control UI tab disconnected, browser background-throttled, you weren’t connected at that moment, etc.).

On VPS setups, the most reliable fix is to forward exec approvals to Telegram so you never miss them due to a flaky dashboard connection:
https://docs.openclaw.ai/tools/exec-approvals (section “Approval forwarding to chat channels”)

#

If you want, paste your channels.telegram config block (redact secrets), and I’ll tell you the exact approvals.exec.targets line for your Telegram “to” value.

2) The #1 thing that still triggers approvals: shell chaining

If your agent is still doing this:

source … && export … && op …

…then it’s no longer “run the op binary”, it’s “run a shell script line”, and approvals will keep happening unless you allowlist the shell (which you correctly don’t want).

Now that you’ve confirmed the token is in the service env, make sure the agent runs only:

  • op item create …
  • op item list …
  • op item get …
  • op read …

No source, no export, no &&, no pipes.

#

One small improvement: change the allowlist entry from ~/.local/bin/op to the absolute path /home/openclaw/.local/bin/op (same thing, just less ambiguity).

3) “Agent responds too slowly” — most common causes

Two big ones in your situation:

A) It’s waiting on approvals/timeouts

If it runs something that triggers approval and you miss the dialog, it’ll sit there until it times out → feels like “the agent is slow”.

Forwarding approvals to Telegram fixes that class of delay.

B) VPS resource / model latency

Quick checks on the droplet:

uptime
free -h

And check logs during a slow interaction:

#
openclaw logs --follow

If you paste ~30–60 lines around a slow message (timestamps included, redact secrets), I can tell you if it’s model latency/retries vs tool waits vs approvals.

Also worth doing: you’re on 2026.2.12 — updating tends to improve Control UI + reliability over time:
https://docs.openclaw.ai/install/updating

#

That’s normal: the CLI/UI will often pretty-print paths using ~ (home-dir contraction) even if the file stores the expanded absolute path.

So:

  • ~/.local/bin/op (display)
  • /home/openclaw/.local/bin/op (on disk)

…are the same thing as long as the approvals are evaluated in the openclaw user context (which your systemd unit does: User=openclaw).

If you want to remove any ambiguity anyway

You can force it to be absolute by removing the ~ entry and re-adding the absolute one:

openclaw approvals allowlist remove --gateway --agent main "~/.local/bin/op"
openclaw approvals allowlist add --gateway --agent main "/home/openclaw/.local/bin/op"
openclaw approvals get --gateway
#

(If the remove command complains about not finding the exact string, just edit via the Control UI → Nodes → Exec approvals, or openclaw approvals set --gateway --file ....)

But functionally: you’re fine as-is. The bigger determinant of “will it prompt” is still whether the agent runs op ... directly vs doing source/export/&& shell text.

#

Don’t try to paste a live --follow stream. Grab a small, time-bounded slice around one slow interaction.

Here are 3 good ways (pick one):

Option A (easiest): reproduce once, then paste last ~200 lines

  1. In one terminal, run:
openclaw logs --follow
  1. Send a message to your agent that you know is “slow”.
  2. As soon as you see the agent finally respond (or you notice it stalled), stop the log stream with Ctrl+C.
  3. Then run:
openclaw logs | tail -n 200

Paste that output (redact secrets).

#

Option B (best signal): journalctl slice for the systemd service

Since you’re running openclaw.service via systemd, this is clean:

  1. Right before you send a “slow” message, note the time (UTC is fine).
  2. After it responds, run:
sudo journalctl -u openclaw.service --since "10 minutes ago" --no-pager | tail -n 300

Paste that.

(If 10 minutes is too much noise, change it to "2 minutes ago".)

Option C (keyword filter): only show model/tool/approval-ish lines

This is handy when logs are super chatty:

sudo journalctl -u openclaw.service --since "10 minutes ago" --no-pager \
#
  | egrep -i "error|warn|rate|429|timeout|retry|model|llm|exec|approval|tool|telegram|latency|slow|queue" \
  | tail -n 200

Redaction guidance (important)

Before pasting, remove anything that looks like:

  • tokens / API keys (sk-..., OP_SERVICE_ACCOUNT_TOKEN=..., OPENCLAW_GATEWAY_TOKEN=...)
  • long bearer strings
  • your full gateway URL if you don’t want it public

Once you paste ~100–300 lines around the slow period, I can usually tell immediately if it’s:

  • rate limiting/retries from the model provider
  • CPU/RAM pressure on the droplet
  • approval waits/timeouts
  • Telegram delivery/backpressure
  • reconnect loops
deep thistleBOT
#

That log snippet is useful, but it’s not enough to diagnose “slow responses” yet — it only shows one concrete problem:

What your logs do show

At 04:38:27 the agent tried to read a daily memory file that doesn’t exist:

  • read failed: ENOENT ... '/home/openclaw/.openclaw/workspace/memory/2026-03-01.md'

If the agent keeps trying this repeatedly, it can absolutely add annoying latency (failing tools + retries + extra reasoning).

Quick safe fix (create the folder + today’s file)

Run as root on the droplet:

sudo -u openclaw mkdir -p /home/openclaw/.openclaw/workspace/memory
sudo -u openclaw touch /home/openclaw/.openclaw/workspace/memory/$(date -u +%F).md
#

Then try one slow interaction again and see if it improves.


How to capture the “slow response” logs properly

Your egrep ... | tail filtered out almost everything. Instead, grab a tight time window around one slow request.

  1. Reproduce the slowness once (send a message on Telegram).
  2. Immediately run this (adjust times around when you sent it):
sudo journalctl -u openclaw.service \
  --since "2026-03-01 04:37:00" --until "2026-03-01 04:41:00" \
  --no-pager

Paste that whole chunk (you can redact tokens/keys).

#

If you don’t want to deal with exact timestamps, do:

sudo journalctl -u openclaw.service --since "5 minutes ago" --no-pager | tail -n 400

Also grab 2 quick system health checks (often the real cause on small droplets)

uptime
free -h

Post those + the 4–5 minute journal slice and I can tell you if the delay is:

  • model/provider latency / retries / rate limits
  • the gateway being CPU/RAM starved
#
  • approval waits/timeouts
  • something looping in tools (like the missing memory file)
#

You shouldn’t have to create a “daily memory file” manually — that log is just telling us the agent tried to read a journal-style file and it wasn’t there, so the read tool returned ENOENT. Two important points:

1) Why is it looking for a memory file at all?

That behavior doesn’t come from “core OpenClaw memory” (the semantic memory stuff). It usually comes from the agent’s own prompt/instructions (or a skill) that says something like “keep a daily log in workspace/memory/YYYY-MM-DD.md” and tries to read it at the start of a run.

If that file doesn’t exist, it’s not fatal, but it can add latency if the agent keeps retrying/looping.

Quick workaround is totally harmless:

sudo -u openclaw mkdir -p /home/openclaw/.openclaw/workspace/memory
sudo -u openclaw touch /home/openclaw/.openclaw/workspace/memory/$(date -u +%F).md

Long-term “real” fix is: remove/adjust whatever instruction is making it do that (if you want, we can hunt it down after).

2) Why March 1 when your laptop is Feb 28?

Because your VPS is almost certainly running in UTC, and OpenClaw logs are timestamped in UTC. So it can already be Mar 1 UTC while it’s still Feb 28 in your local timezone.

#

Check on the droplet:

date
date -u
timedatectl | sed -n '1,6p'

You can change the server timezone for your own convenience, but even if you do, many systems still use UTC internally for filenames/logs.


If you want, paste the output of timedatectl (first ~6 lines is enough) and I’ll confirm what timezone the droplet is using.

#

That explains it: you do have a “daily log” file mechanism already, but it only exists for 2026-02-27:

~/.openclaw/workspace/memory/2026-02-27.md

So when the agent tries to read today’s file (on the server), it 404s and you get that ENOENT.

Why you “have to create it”

You don’t have to for OpenClaw to function — this is almost certainly coming from your agent instructions / a skill / a template prompt that uses a “daily memory/journal file” convention.

Core OpenClaw memory ≠ this file. This is just a plain markdown file in the workspace that some prompts like to use.

If you want the errors/latency to stop without changing prompts, just create the expected file for “today” on the VPS.

Why it’s looking for Mar 1

Because your VPS is running in UTC, and OpenClaw uses UTC timestamps in logs. It can be Mar 1 on the VPS while it’s still Feb 28 on your laptop.

#

Check:

date
date -u

Quick fix (safe)

Create the file for the VPS’s current UTC date:

sudo -u openclaw bash -lc 'd=$(date -u +%F); mkdir -p ~/.openclaw/workspace/memory; touch ~/.openclaw/workspace/memory/$d.md; ls -l ~/.openclaw/workspace/memory/$d.md'

Longer-term fix (optional)

If you don’t want the agent doing this at all, we can remove the instruction that tells it to read/write workspace/memory/YYYY-MM-DD.md — but we’d need to find where that instruction lives (often in your agent’s system prompt / workspace docs). A quick way to locate it is:

#
sudo -u openclaw rg -n "workspace/memory|/memory/\\d{4}-\\d{2}-\\d{2}\\.md" /home/openclaw/.openclaw/workspace -S

If you paste whatever file it matches (just the relevant lines), I can tell you what to change.

#

Good — that should eliminate the repeated ENOENT tool-failure noise.

To test “slowness” in a way we can actually diagnose, do one controlled run and capture a tight log window.

Step 1: run a simple baseline “fast” request

From Telegram, send something trivial like:

“Reply with exactly: OK”

Note how long it takes.

Step 2: run a “tool” request that used to be slow

Send one 1Password command request, e.g.:

“Run op --version and paste the output.”

(or your create/list test)

Step 3: capture a 2–3 minute log slice for each test

#

Right after you send the message (or right after it finishes), run on the VPS:

sudo journalctl -u openclaw.service --since "3 minutes ago" --no-pager | tail -n 400

Paste that output here.

That slice usually contains:

  • when the Telegram message arrived
  • when the model request started/ended
  • any retries / rate limits
  • tool calls + durations
  • any “waiting for approval” / timeouts

Step 4: also paste droplet resource stats

Run:

#
uptime
free -h

What I’ll look for in the logs

  • Long gaps before the model call → queue/backpressure or event loop blocked
  • Model call takes ages → provider latency / rate limiting / retries
  • Tool call takes ages → op command or network delays
  • Approval pending → missed prompt causing timeout
  • Frequent reconnect logs → Telegram or gateway connectivity issues

If you want to be extra precise about timing, tell me “I sent the message at HH:MM UTC” (or run date -u before you send it).

#

Those timings are not normal (1 min for “OK” means the delay is happening before it even does any real work).

Since you already fixed the missing daily memory file, the next step is to see where the time is going: Telegram → Gateway → LLM call → reply.

0) Quick sanity: re-test once now that 2026-03-01.md exists

Send “Reply with exactly: OK” again. If it suddenly drops to a few seconds, the missing-file loop was a big part of it. If it’s still ~1 min, keep going.


1) Capture a clean 3–5 minute log slice (most important)

Right after you send a slow message (or right after it finally replies), run:

sudo journalctl -u openclaw.service --since "5 minutes ago" --no-pager | tail -n 500

Paste that output here.

#

If you’re worried about secrets: journal output shouldn’t print your env tokens, but still quickly scan before pasting and redact anything that looks like a key/token.

What I’m looking for in that slice:

  • how long between “telegram message received” and “llm request started”
  • how long the LLM request takes (often logged as a duration)
  • any retries / 429 rate limits / timeouts
  • any approvals waiting/timeouts

2) Check whether the droplet is underpowered (very common)

Run and paste:

uptime
free -h
#
ps -o pid,cmd,%cpu,%mem --sort=-%cpu | head -n 10

If RAM is tight and it’s swapping, everything will feel “laggy” and model calls can backlog.


3) Check for service restarts / crashes (causes long gaps + missed dialogs)

sudo journalctl -u openclaw.service -b --no-pager | egrep -i "restarting|killed|oom|fatal|panic|uncaught" | tail -n 50

Once you paste (1) + (2), I can tell you which bucket you’re in:

  • model/provider latency or rate limiting
#
  • CPU/RAM pressure on the droplet
  • approval waits/timeouts
  • service restart / reconnect churn
  • something looping in tools
#

Blank 2026-03-01.md is totally fine — the point was just to stop the “file not found” tool error loop.

15 seconds is a lot better than 60s, and it strongly suggests the missing-file retries were part of the delay. That said, 15s for “OK” is still slower than it should be, so there’s probably another latency source (model/provider latency, droplet load, or retries).

Next: measure where the 15 seconds went (one more log slice)

Right after you run another “Reply with exactly: OK”, grab:

sudo journalctl -u openclaw.service --since "2 minutes ago" --no-pager | tail -n 300

Paste that here. In that chunk I’m looking for timestamps around:

  • telegram inbound message
  • model request start/end (or retries)
  • outbound send

Also paste droplet health (often the culprit)

#
uptime
free -h

If it’s consistently ~10–20s even when the VPS is idle

That’s usually model/provider latency (or retry/backoff). In that case, the fix is typically “use a faster model / set better fallbacks” (models docs: https://docs.openclaw.ai/concepts/models — and for what’s fastest right now, ask in the server’s #models channel since it changes quickly).

Send the log slice + uptime/free -h and we can pinpoint it.

#

Those VPS stats look healthy:

  • load average 0.00 → not CPU-bound
  • ~1.1GiB “available” RAM, no swap → not memory-starved

So the remaining ~15s delay is very likely outside “droplet performance”, i.e.:

  1. Model/API latency (slow provider response, retries, rate limits/backoff)
  2. Telegram delivery latency / reconnect churn (less common, but happens)
  3. Gateway doing extra work per message (tool retries, long system prompts, etc.)

Next step: get the right log slice for one “OK” message

Do this immediately after you send “Reply with exactly: OK” once:

sudo journalctl -u openclaw.service --since "2 minutes ago" --no-pager | tail -n 400
#

If that’s still too noisy, use this filtered version (keeps timing-ish lines):

sudo journalctl -u openclaw.service --since "2 minutes ago" --no-pager \
  | egrep -i "telegram|inbound|outbound|send|deliver|llm|model|completion|rate|429|retry|timeout|error|warn" \
  | tail -n 250

What I’m trying to see is: where the 15 seconds sits:

  • before the LLM call (queue / processing)
  • inside the LLM call (provider latency/retries)
  • after (Telegram send/delivery)

Also run these two (quick)

openclaw channels status --probe
#
openclaw status

If logs show the LLM call itself takes ~10–20s consistently

That’s basically “your current model/provider is slow for your region / current load / your prompt size”. The fix is usually switching the agent to a faster model (docs: https://docs.openclaw.ai/concepts/models). For which model is fastest right now, ask in the server’s #models channel (it changes constantly, and I don’t want to guess and mislead you).

Paste the 2‑minute journal slice and I’ll call out exactly which bucket it is.

#

-- No entries -- from journalctl is actually plausible here.

Even though your systemd unit has StandardOutput=journal, OpenClaw often writes the “interesting” stuff to its own gateway log file (and only emits to stdout/journal on warnings/errors). So a clean “OK” round-trip can produce zero journal lines.

Use OpenClaw’s log tail (best for this case)

Right after you run a slow “Reply with exactly: OK”, run:

openclaw logs --limit 400 --local-time | tail -n 200

If that’s still too chatty, filter it:

openclaw logs --limit 800 --local-time \
  | egrep -i "telegram|inbound|outbound|deliver|send|llm|model|completion|rate|429|retry|timeout|error|warn|took|ms" \
#
  | tail -n 250

That’s the chunk I need to see where the 10–15 seconds is spent.

If even openclaw logs doesn’t show timing info: temporarily increase log level

Add this to /opt/openclaw.env:

OPENCLAW_LOG_LEVEL=debug

Then:

sudo systemctl restart openclaw.service
#

Re-test once, then grab:

openclaw logs --limit 600 --local-time | tail -n 300

(After we’re done debugging, you can revert to info-level; debug can get noisy.)

Interpretation (based on your VPS stats)

Since CPU/RAM look fine, a steady 10–15s for “OK” is most commonly model/provider latency or retries. The logs will confirm that (you’ll see long “LLM request took …ms” or rate-limit/retry lines).

#

Run it after you get the response (or after you decide it’s “taking too long”).

When I say “right after sending”, I mean “right after the interaction finishes”, so the relevant log lines are still near the end of the file and easy to grab.

A good workflow is:

  1. Send: Reply with exactly: OK
  2. Wait until the reply arrives on Telegram
  3. Immediately run:
openclaw logs --limit 400 --local-time | tail -n 200

If you wait 10+ minutes and other stuff happens, the interesting lines get buried and the slice becomes harder to interpret.

If the message is still hanging, you can also run it while waiting (it won’t hurt), but the most useful slice is the one that includes the completion/send.

#

Once you paste that log tail, I’ll point to the exact timestamp gap that accounts for the 10–15 seconds.

#

Totally expected that you can’t find Reply with exactly: OK in the logs.

OpenClaw generally does not log the raw message text (privacy + to avoid accidentally writing secrets into logs). So searching for your exact prompt often won’t match anything even when the message was processed.

If you want something you can safely paste here, do summary-style extraction instead of raw logs.

Safe-ish “signal only” snippet (no message bodies)

Run this right after you get a response:

openclaw logs --limit 1500 --plain --no-color --local-time \
  | egrep -i "model\.usage|message\.processed|queue\.lane|telegram|deliver|send|429|rate|retry|timeout|error|warn" \
  | tail -n 200

That usually captures timing / retries / queueing / delivery without dumping payloads.

#

If that still doesn’t show anything useful

Use channel-focused logs (often less noisy):

openclaw channels logs --channel telegram | tail -n 200

Docs: https://docs.openclaw.ai/cli/logs and https://docs.openclaw.ai/cli/channels

Why your journalctl approach showed nothing

Your service can be running fine while not emitting anything new to journald for a clean request; a lot of detail goes to the gateway’s rolling file log, which openclaw logs tails.

If you paste the output of the first command (200 lines), I can tell you whether the 10–15s is:

  • inside the model call (provider latency / retries),
  • queueing before the model,
  • or slow Telegram delivery.
deep thistleBOT
#

In Discord you can attach files the normal way:

  • Drag & drop the file into the thread message box, or
  • Click the “+” / paperclip next to the message box → Upload a File → select it → send.

If the file is on your VPS (not on your laptop), easiest is to dump the relevant snippet into a paste:

openclaw logs --limit 1500 --plain --no-color --local-time \
  | egrep -i "model\.usage|message\.processed|queue\.lane|telegram|deliver|send|429|rate|retry|timeout|error|warn" \
  | tail -n 200 > /tmp/openclaw-log-snippet.txt

Then either:

  • download /tmp/openclaw-log-snippet.txt to your laptop and attach it, or
  • paste the contents into Pastebin (or similar) and send the link.
#

If you tell me whether you can SSH from your laptop to the droplet, I can give you the exact one-liner to copy the file down (e.g. scp ...).

#

I can’t access that LimeWire link’s actual file contents from here (it’s a JS-driven download page; web_fetch only sees the “Download logs.txt” wrapper, not the text file). So I can’t read the log from that URL.

Best options:

Option A (best): attach the file directly in this Discord thread

Use the Discord upload button (paperclip / “+”) or drag & drop logs.txt into the message box and send it here.

Option B: re-upload somewhere with a raw text URL (Pastebin / GitHub Gist)

On the VPS, generate a small snippet file first, then paste/upload that:

openclaw logs --limit 2000 --plain --no-color --local-time \
  | egrep -i "model\\.usage|message\\.processed|queue\\.|telegram|deliver|send|429|rate|retry|timeout|error|warn" \
  > /tmp/openclaw-slow-snippet.txt

Then either attach /tmp/openclaw-slow-snippet.txt in Discord, or paste it to Pastebin/Gist and share the raw link.

#

Option C: if you’re worried about secrets, auto-redact before sharing

sed -E \
  -e 's/(OP_SERVICE_ACCOUNT_TOKEN=)[^[:space:]]+/\\1REDACTED/g' \
  -e 's/(OPENCLAW_GATEWAY_TOKEN=)[^[:space:]]+/\\1REDACTED/g' \
  /tmp/openclaw-slow-snippet.txt > /tmp/openclaw-slow-snippet.redacted.txt

Attach ...redacted.txt.

If you can attach either the snippet (Option B) or the full file directly in Discord (Option A), I’ll point to exactly where the 10–15s delay is happening (queueing vs model call vs Telegram send).

#

Got it — I can read the Pastebin and there are two big findings (plus a “what’s sensitive” answer).

Does the paste contain sensitive info?

I don’t see obvious API keys / tokens in what you posted (good). But it does contain some stuff you probably don’t want publicly indexed forever:

  • your Telegram bot username: @openclaw3_bookkeeper_bot
  • a Telegram numeric id target: telegram:8577900743
  • your 1Password vault name: "Shared with Yug"
  • item titles like "AutoApproved2 - Test Entry - OpenClaw"
  • internal run/session ids

So: not “secrets leaked”, but still personal metadata. I’d delete the paste once we’re done and re-share redacted snippets next time.

1) Why approvals “sometimes don’t show up” (this is clear in the logs)

You have repeated errors:

403: Forbidden: bots can't send messages to bots

#

and specifically:

exec approvals: failed to deliver to telegram:8577900743

That means your exec-approval forwarding is trying to DM a Telegram bot account, which Telegram forbids. So any approval prompts forwarded to that target will silently fail, which feels like “the approval popup didn’t show”.

Fix: configure approvals forwarding to your human Telegram chat id, or simpler: don’t use a fixed target at all.

Best approach on Telegram is to forward to the same session that triggered the exec:

// ~/.openclaw/openclaw.json
{
  approvals: {
    exec: {
      enabled: true,
#
      mode: "session",
      agentFilter: ["main"],
    },
  },
}

Docs: https://docs.openclaw.ai/tools/exec-approvals (Approval forwarding to chat channels)

2) Why it feels slow (your droplet is fine — it’s model/provider latency)

Your VPS isn’t CPU/RAM bound (you showed load ~0, plenty of RAM), and your logs show lots of runs like:

  • durationMs=63154 (63s)
  • durationMs=52218 (52s)
  • durationMs=93968 (94s)
  • durationMs=117805 (118s)
#

And they’re consistently using:

provider=custom-integrate-api-nvidia-com model=moonshotai/kimi-k2.5

So the main delay is simply LLM round-trip time (plus sometimes extra tool-call churn — you also have read tool called without path warnings, which usually means the model is producing malformed tool calls and the agent has to recover).

What to do:

Bonus: there’s also an op command error in your logs

You have:
unknown command "version" for "op"

So somewhere the agent ran op version (wrong) instead of op --version (correct). Not the main latency issue, but it can cause extra back-and-forth.

If you paste your current approvals.exec config block (redact tokens), I can point out exactly where that telegram:8577900743 target is coming from and what to change.

#

If you posted it anonymously (not while logged into a Pastebin account), Pastebin usually treats that paste as not owned by your account, so you won’t get a delete button later.

What you can do:

1) Check if you still have the delete key / edit link

When you create an anonymous paste, Pastebin often shows (or sometimes emails) a “delete key” or gives you a special edit/delete URL right after posting. If you still have that tab open or browser history, look for a URL containing something like:

  • pastebin.com/edit/...
  • a ?delete= / delete token
  • or a “Delete” link on the page you originally saw after creation

If you find that, open it and you can delete.

2) If you don’t have the delete key, contact Pastebin support

Without the delete token, you generally can’t delete an anonymous paste yourself. Your options are:

  • Use Pastebin’s Report / DMCA / privacy request flow (explain it contains accidentally shared personal data and you’re the author).
  • Or email their support with the URL and ask for removal.
#

3) Mitigation right now (recommended)

Even if you can’t delete it immediately:

  • Remove the link from this Discord thread if possible (edit your message if you can).
  • Rotate/redact anything that might be sensitive (you didn’t leak tokens in that paste from what I saw, so you probably don’t need to rotate keys).
  • For future logs: use a redacted snippet first (I can give you a one-liner that strips common token patterns before you upload).

If you want, paste a screenshot of what you see when you open the paste (blur the content), and I’ll tell you exactly where the “report” / “flag” option is on that page.

#

I can help you write this as a polished blog post via a quick 3‑stage workflow (context → draft → reader test). If you want that, tell me: who’s the audience (OpenClaw beginners vs power users) and what install target (DigitalOcean 1‑click only vs “any Linux VPS”).

Meanwhile, here’s a solid v1 article you can copy/paste and publish.


Setting up 1Password with OpenClaw (so your agent can create & store credentials without constant approvals)

If you’re running OpenClaw on a VPS and you want your agent to generate credentials and store them in 1Password automatically, the clean approach is:

  • Use 1Password Service Accounts (not your personal login)
  • Put the OP_SERVICE_ACCOUNT_TOKEN into the Gateway service environment
  • Allowlist only the op binary in OpenClaw Exec Approvals
  • Make the agent run direct op … commands (no source … && export … && … shell chaining)

This keeps things automated without allowlisting the whole shell (which is too dangerous).

#

Why approvals kept popping up (the root cause)

OpenClaw exec approvals allowlists match the resolved executable path (the binary), not command text.

So these do not help:

  • op item create
  • export OP_SERVICE_ACCOUNT_TOKEN
  • source ~/.openclaw/workspace/...

Also, commands like:

source ~/.openclaw/workspace/.secrets/1password.env && export OP_SERVICE_ACCOUNT_TOKEN && op item create ...

force the exec request to be treated as shell text (builtins + &&). In allowlist mode that typically becomes an allowlist miss unless you allowlist bash/sh (which you should avoid).

The fix is to make OP_SERVICE_ACCOUNT_TOKEN available to the OpenClaw service without needing source/export.

#

Prerequisites

  • A 1Password account that supports Service Accounts
  • A dedicated vault for the agent (recommended)
  • OpenClaw Gateway running on Linux (systemd service on a VPS is common)
  • 1Password CLI (op) installed on the same machine where OpenClaw runs host exec (usually the gateway host)

Security note: a service account token is effectively “the keys” to whatever vaults it can access. Keep it scoped.


Step 1 — Install the 1Password CLI (op) on the gateway host

Install op and confirm it works:

#
op --version

(How you install it is up to you; just ensure the final binary path is stable, e.g. /home/openclaw/.local/bin/op.)


Step 2 — Put OP_SERVICE_ACCOUNT_TOKEN into the Gateway’s environment (no shell chaining)

General OpenClaw approach (works broadly)

OpenClaw can load env vars from ~/.openclaw/.env (see docs):
https://docs.openclaw.ai/help/environment

DigitalOcean 1‑click “OpenClaw droplet” approach (what you have)

Your service definition shows:

#
EnvironmentFile=/opt/openclaw.env
User=openclaw

So put this into /opt/openclaw.env (no export keyword):

OP_SERVICE_ACCOUNT_TOKEN="ops_xxx_your_token_here"

Then restart:

sudo systemctl restart openclaw.service
#

Sanity check without printing the token:

pid="$(systemctl show -p MainPID --value openclaw.service)"
sudo tr '\0' '\n' < "/proc/$pid/environ" \
  | grep -q '^OP_SERVICE_ACCOUNT_TOKEN=' \
  && echo PRESENT || echo MISSING

Step 3 — Allowlist only the op binary (and nothing else)

Exec approvals are stored per host at:

~/.openclaw/exec-approvals.json

Use the CLI (recommended) so you don’t fight JSON formatting:

#
# find the actual binary path the gateway user sees
sudo -u openclaw which op

# allowlist it for the gateway + main agent
openclaw approvals allowlist add --gateway --agent main "/home/openclaw/.local/bin/op"

# verify
openclaw approvals get --gateway

Docs: https://docs.openclaw.ai/tools/exec-approvals and https://docs.openclaw.ai/cli/approvals

What NOT to allowlist

Avoid allowlisting:

  • /bin/bash / /usr/bin/sh (effectively “run arbitrary shell text”)
  • python3 (effectively “run arbitrary code”)
#
  • cat (easy file exfiltration)
  • curl (broad outbound network)

If your goal is “agent can use 1Password”, the allowlist can be just op.


Step 4 — Make the agent run direct op … commands (no source, no export, no &&)

Once the token is in the service environment, your agent should run commands like:

op item create --vault "Your Vault" --category password --title "Example" username="u" password="p"
op item list --vault "Your Vault"
op item get --vault "Your Vault" "Example"

No environment setup per command needed.

#

Quick test prompt you can send your agent

Run these commands using host exec. Do not use source, export, &&, pipes, or redirections.

  1. op --version
  2. op vault list (confirm my vault exists)
  3. op item create --vault "Shared with Yug" --category password --title "Test Entry - OpenClaw" username="openclaw-test-user" password="openclaw-test-pass"
  4. op item list --vault "Shared with Yug"

If you still get approval prompts at this point, it usually means the exec request isn’t resolving to op (i.e., the agent is still doing shell chaining).


Step 5 (optional) — Make approval prompts reliable on a VPS

If you forward exec approvals to Telegram, make sure you’re not trying to message a bot account (Telegram error: “bots can’t send messages to bots”). The simplest reliable mode is forwarding to the same chat session that triggered the exec:

{
#
  approvals: {
    exec: {
      enabled: true,
      mode: "session",
      agentFilter: ["main"],
    },
  },
}

Docs: https://docs.openclaw.ai/tools/exec-approvals (Approval forwarding to chat channels)


Troubleshooting checklist

  • openclaw approvals get --gateway shows your op allowlist entry
#
  • The gateway process actually has OP_SERVICE_ACCOUNT_TOKEN in its environment (check with /proc/<pid>/environ + grep -q)
  • The agent is running op … directly (not shell text)
  • If things feel slow: it’s often model/provider latency, not VPS CPU/RAM

Safety / operational advice

  • Use a dedicated vault for the agent.
  • Give the service account the minimum vault access it needs.
  • Rotate the service account token if it ever lands in logs/paste sites.
  • Be cautious with fully automating website signups: some sites forbid automated account creation in their ToS.

If you tell me:

  1. do you want this blog to be DO 1‑click specific or generic Linux?
  2. what’s your preferred title + tone (super short “quickstart” vs long “deep dive”)?
#

…I’ll revise v1 into a polished final post (with a clean intro, diagrams, and a short troubleshooting matrix).

#

1) Structure: happy path first + pitfalls after

Yep — that’s the best format for this topic. I’d do:

  1. What you’ll achieve (2–3 bullets)
  2. Happy path (copy/paste steps)
  3. Verification (quick tests)
  4. Common pitfalls (symptom → cause → fix)
  5. Security notes (short but clear)

If you want, I can rewrite the draft I sent into exactly that layout (shorter + more “do this, then this”).


2) Copy/paste to a Twitter article

“Twitter articles” depends on what you mean:

  • If you mean X (Twitter) Articles: that feature isn’t available to every account and the UI changes a lot. If you have it, it’s usually: Post composer → Article / Longform. Then you paste Markdown-ish text and format headings manually.
#
  • If you mean a long X post/thread: you’ll need to split the article into a thread (I can do that too).
  • If you mean publish elsewhere and share on X: common flow is publish on Medium/Dev.to/Substack/Notion → share the link on X.

Tell me which of these you want:

  1. X Article (native)
  2. X thread
  3. External blog + link on X

…and I’ll format it appropriately (X threads need very different formatting).


If you want the “happy path” version now (ready to paste)

Here’s a tighter Happy Path section you can paste into an article right away:

Happy path: OpenClaw + 1Password Service Account (no approval spam)

#

Goal: Let OpenClaw run op item create/list/get/read without prompting, without allowlisting bash/sh.

Step 1 — Install op on the gateway host

op --version

Step 2 — Put OP_SERVICE_ACCOUNT_TOKEN into the Gateway service environment

On the DigitalOcean 1‑click OpenClaw image, the systemd unit uses:

EnvironmentFile=/opt/openclaw.env

Add:

OP_SERVICE_ACCOUNT_TOKEN="ops_..."
#

Restart:

sudo systemctl restart openclaw.service

Verify without printing the token:

pid="$(systemctl show -p MainPID --value openclaw.service)"
sudo tr '\0' '\n' < "/proc/$pid/environ" | grep -q '^OP_SERVICE_ACCOUNT_TOKEN=' && echo PRESENT || echo MISSING

Step 3 — Allowlist only the op binary for agent main

Find the binary path:

sudo -u openclaw which op
#

Allowlist it:

openclaw approvals allowlist add --gateway --agent main "/home/openclaw/.local/bin/op"
openclaw approvals get --gateway

Step 4 — Use direct op … commands (no source, no export, no &&)

Now the agent should run:

op item create --vault "Shared with Yug" --category password --title "Test Entry - OpenClaw" username="u" password="p"
op item list --vault "Shared with Yug"
op item get --vault "Shared with Yug" "Test Entry - OpenClaw"

If you confirm you want X thread vs X Article, I’ll rewrite the full blog into the final “happy path + pitfalls” version and format it for that destination.

deep thistleBOT
#

Before diving into knobs: quick threat-model framing for 1Password + agents
If you’re letting an agent run op ..., assume anything it can read/write in that vault has a non‑trivial chance of leaking (logs, prompts, compromised agent, misrouted outputs, etc.). Strongly recommend: dedicated vault + service account, least-privilege, and only put creds you can live with rotating/losing (i.e. “automation creds”, not your primary human account / crown jewels).

Now, why your auto-approvals aren’t “sticking”:

1) Your exec-approvals.json is (very likely) not being applied because the allowlist entries don’t match how OpenClaw matches

Exec approvals allowlists match resolved executable paths (glob), not “command strings”.
So entries like:

  • "pattern": "op item create"
  • "pattern": "export OP_SERVICE_ACCOUNT_TOKEN"
  • "pattern": "~/.openclaw/workspace/.secrets/1password.env"

…won’t do what you expect. OpenClaw is looking for patterns like:

  • /home/openclaw/.local/bin/op (or wherever op actually is)
  • /usr/bin/curl, /usr/bin/unzip, /usr/bin/mv, etc.

Docs: https://docs.openclaw.ai/tools/exec-approvals (see “Allowlist (per agent)”)

#

2) Your pasted file looks malformed JSON

In your paste, the op item get entry is missing closing fields/braces and there’s a stray { before the next item. If the real file on disk looks like that, the approvals service may fail to load it (and you’ll keep getting prompts/denies).

3) You restarted the gateway, but approvals are enforced on the execution host

Approvals live on the machine where exec actually runs (host=gateway or host=node), at:
~/.openclaw/exec-approvals.json

So the two most common “I edited it but nothing changed” causes are:

  • you edited the file under the wrong Linux user (e.g. root vs openclaw)
  • your agent is executing on a node host, but you edited the gateway approvals (or vice versa)

Fast sanity check on the droplet:

openclaw --version
openclaw approvals get --gateway

Docs: https://docs.openclaw.ai/cli/approvals

#

4) Why you got “tonnes” of approvals even for one task

Your install command chains a bunch of binaries (curl && unzip && mv && chmod && ...). In security=allowlist, every segment’s executable must be allowlisted, otherwise you’ll get prompted again and again.


What I’d do (minimal + reliable)

  1. Use the Control UI to add allowlist entries so it writes the correct executable paths for you:
    Control UI → Nodes → Exec approvals (select Gateway vs Node correctly)
    Docs: https://docs.openclaw.ai/tools/exec-approvals#control-ui-editing

  2. Add allowlist patterns for the binaries, e.g.:

  • /home/openclaw/.local/bin/op (or the real resolved path)
  • /usr/bin/curl
  • /usr/bin/unzip
  • /usr/bin/mv
  • /usr/bin/chmod
#
  1. Keep ask: "on-miss" until you’ve stabilized the workflow. If you set ask: "off", you’ll stop seeing prompts, but new binaries will just get denied (which can feel like “it’s broken” unless you expect it).

About the missing approval pop-up in Control UI

If you keep missing the modal, enable approval forwarding to chat and approve from Discord with /approve … (much harder to miss than a flaky websocket modal):

Docs + config example: https://docs.openclaw.ai/tools/exec-approvals#approval-forwarding-to-chat-channels


If you paste these two outputs, I can tell you exactly what’s being applied and where the mismatch is:

  1. openclaw --version
  2. openclaw approvals get --gateway (redact the socket token)

Also: paste one exact command line that the agent is still asking you to approve (the “resolved executable path” shown in the approval dialog is especially useful).

#

Here’s a full, beginner-friendly blog post you can publish (Markdown). It’s written to be readable even if someone’s only “kinda comfortable” with SSH, and it uses a simple story + short sections for skim-readers.


Stop copy‑pasting API keys: use 1Password (Service Accounts) with OpenClaw

You know that moment when your OpenClaw agent finally works… so you celebrate by pasting an API key into a config file on a random cloud server?

Yeah. That’s the part that should make you a little nervous.

This guide shows a safer pattern that still feels “easy mode”:

  • Put your OpenClaw secrets in a dedicated 1Password vault
  • Give OpenClaw access using a 1Password Service Account (not your personal account)
  • Let OpenClaw fetch secrets at runtime using the 1Password CLI (op)

No extra servers. No secret sprawl. Much easier to rotate when (not if) you need to.

#

TL;DR (for fast readers)

You’ll do four things:

  1. Create a dedicated vault (example: OpenClaw Automation)
  2. Create a 1Password Service Account that can read that vault
  3. Install the op CLI on your OpenClaw machine and set OP_SERVICE_ACCOUNT_TOKEN
  4. Configure OpenClaw “Secrets Management” to read API keys via op read ...

The mindset (this is the part most guides skip)

Don’t use your personal 1Password vault for automation

For agent automation, assume the environment is “leak-prone”:

  • logs get copied around
#
  • terminals get screenshotted
  • tokens end up in shell history
  • a future bug prints more than it should
  • you accidentally share a config file

So treat the “automation vault” like a containment zone:
only store credentials you can rotate quickly and would survive if exposed.

That’s why this guide uses:

  • a dedicated vault
  • a service account
  • least privilege access

(1Password explicitly positions service accounts for automation and least privilege use cases.)


#

What you’re building

OpenClaw has a Secrets Management system that can resolve secret values from an exec provider (a trusted CLI command). We’ll configure it so OpenClaw runs:

  • op read op://<Vault>/<Item>/<field>

…and uses the output as the API key for your model provider.

OpenClaw docs reference:


What you need (beginner checklist)

  • A 1Password account where you can create Service Accounts (or an admin who can)
  • SSH access to the machine running OpenClaw (DigitalOcean Marketplace droplet is perfect)
#
  • OpenClaw already running on that machine
  • 10–15 minutes

Step 1: Create a dedicated vault (in 1Password)

Create a vault named something like:

  • OpenClaw Automation (good)
  • OpenClaw - Staging and OpenClaw - Prod (better, if you run multiple environments)

Put only automation secrets here.

Examples of “good automation secrets”:

  • a limited-scope OpenAI key (or a project key)
  • a bot token with minimal permissions
#
  • a throwaway account password you can reset quickly

Examples of “don’t do this”:

  • your personal email password
  • the “god mode” AWS root key
  • anything you’d hate to rotate under pressure

Step 2: Create a 1Password Service Account (least privilege)

In 1Password, create a Service Account and grant it access to only the vault you made above.

Important notes from 1Password:

  • Service account permissions/vault access are intentionally rigid (if you need more vaults later, you typically create a new service account).
  • The token is shown once—store it safely immediately.
#

If it’s not installed, install it using 1Password’s official instructions for your OS:
https://developer.1password.com/docs/cli/get-started/

(Keeping install steps “source of truth” with 1Password helps this blog stay evergreen.)


Step 4: Authenticate op using the service account token

1Password Service Accounts authenticate by setting an environment variable:

  • OP_SERVICE_ACCOUNT_TOKEN

Official docs:
https://developer.1password.com/docs/service-accounts/use-with-1password-cli/ (it explicitly calls out OP_SERVICE_ACCOUNT_TOKEN)

The bootstrap problem (be honest about it)

#

You still need to store one secret somewhere: the service account token itself.

That token is the “key to the vault”—treat it like a password:

  • don’t paste it into screenshots
  • don’t commit it
  • don’t leave it in shell history if you can avoid it

Where to put it (two common options)

Option A (simple for beginners): set it in the environment before starting OpenClaw
If you start OpenClaw manually from a shell, you can export it in that shell session.

Option B (better for servers): set it for the OpenClaw service
If OpenClaw runs as a daemon/service, set OP_SERVICE_ACCOUNT_TOKEN in the service environment so it’s always available after reboot.

OpenClaw’s env-var loading rules (useful background):
https://docs.openclaw.ai/help/environment

#

Step 5: Put a secret in the vault

Create an item in the OpenClaw Automation vault, for example:

  • Item name: OpenAI API Key
  • Field: password (or whatever you prefer, but password is common)

You’ll reference it like:

  • op://OpenClaw Automation/OpenAI API Key/password

Step 6: Test op read works on the server (quick sanity check)

#

With OP_SERVICE_ACCOUNT_TOKEN set, run:

op read "op://OpenClaw Automation/OpenAI API Key/password"

You should see the secret printed.

If this fails, fix it here before touching OpenClaw config.

Common failure causes:

  • token not set for the user/process running op
  • service account doesn’t have access to that vault
  • you tried to use a Personal/Private vault (service accounts can’t access those)
  • op is older than required (service accounts require op CLI v2.18.0+)
  • you have 1Password Connect env vars set; they can take precedence (1Password calls this out in their docs)
#

Step 7: Configure OpenClaw to read the API key via 1Password CLI

OpenClaw supports secrets providers. We’ll create an exec provider that calls op read ..., then point the model provider’s apiKey at that secret ref.

Example openclaw.json snippet

This is adapted directly from OpenClaw’s secrets docs (with the path updated to a typical Linux install location). You must set the correct command path for your server.

{
  secrets: {
    providers: {
      onepassword_openai: {
        source: "exec",
#
        command: "/usr/local/bin/op", // change to wherever `op` lives on your server
        args: ["read", "op://OpenClaw Automation/OpenAI API Key/password"],
        passEnv: ["HOME", "OP_SERVICE_ACCOUNT_TOKEN"],
        jsonOnly: false,
      },
    },
  },

  models: {
    providers: {
      openai: {
        baseUrl: "https://api.openai.com/v1",
        models: [{ id: "gpt-5", name: "gpt-5" }],

        // This tells OpenClaw: resolve the apiKey by running the exec provider above
#
        apiKey: { source: "exec", provider: "onepassword_openai", id: "value" },
      },
    },
  },
}

Source (OpenClaw docs):
https://docs.openclaw.ai/gateway/secrets (see “1Password CLI” example)

Two “gotchas” to call out in the blog

  1. command must be an absolute path to the op binary
  2. passEnv must include OP_SERVICE_ACCOUNT_TOKEN (otherwise the provider runs but can’t authenticate)

#

Step 8: Reload OpenClaw and verify

After updating config:

  • restart/reload the OpenClaw Gateway (use whatever method you normally use on your droplet)
  • then run a simple test that forces a model call

If OpenClaw can’t resolve the secret on startup, it should fail fast (that’s good: you’ll notice immediately).


Troubleshooting (copy/paste friendly)

“op works in my SSH session, but OpenClaw can’t read secrets”

That usually means OpenClaw is running as a service without your interactive shell environment.

Fix: ensure OP_SERVICE_ACCOUNT_TOKEN is present in the Gateway process environment, not just your terminal.

#

OpenClaw env precedence reference: https://docs.openclaw.ai/help/environment

“Service account can’t see my vault”

Service accounts can’t access personal/private/default shared vaults. Use a dedicated vault and explicitly grant access.

1Password docs: https://developer.1password.com/docs/service-accounts/get-started/

“It’s slow / rate-limited”

Service accounts have request limits. Also, some op commands make multiple requests. Prefer a single op read ... for runtime resolution (and avoid unnecessary op item list loops).

1Password note on request counts: https://developer.1password.com/docs/service-accounts/use-with-1password-cli/


Security checklist (practical, not perfect)

  • Use a dedicated vault for automation secrets
#
  • Use a service account with least privilege
  • Store only secrets you can rotate quickly
  • Treat the service account token like a password
  • Rotate tokens periodically (and immediately if you suspect exposure)

Bonus: X/Twitter thread version (paste-ready)

1/ The easiest way to leak your API keys is “just for now” copy-pasting them into a server config. I did it too. Here’s a cleaner setup: OpenClaw + 1Password Service Account + op read (no extra servers).

2/ Rule #1: don’t use your personal 1Password vault for automation. Make a dedicated vault like OpenClaw Automation. Assume anything in there might leak someday. Store only rotate-friendly creds.

3/ Create a 1Password Service Account. Give it access to only that vault. Least privilege. (Docs: developer.1password.com → Service Accounts)

4/ Install 1Password CLI (op) on the OpenClaw machine.

#

5/ Set OP_SERVICE_ACCOUNT_TOKEN for the process that runs OpenClaw (not just your SSH session).

6/ Put your API key in the automation vault (example item: “OpenAI API Key”).

7/ Test: op read "op://OpenClaw Automation/OpenAI API Key/password"

8/ Configure OpenClaw Secrets (exec provider) to run op read ..., then point your model apiKey at that SecretRef.

9/ Result: no API keys sitting in configs, easier rotation, cleaner ops.


If you tell me which providers you want in the blog examples (OpenAI only? Anthropic too? OpenRouter?), and whether you want separate “staging vs prod” sections, I can tailor the config snippets and the story beats.

deep thistleBOT
#

Below are only the rewritten parts you asked for (install commands + “where to put it” with real commands + clearer “Step 5” + story-based step titles).
All commands below are written for a typical Ubuntu/Debian DigitalOcean droplet and were accurate as of 2026‑03‑01. If anything drifts, use the official 1Password CLI install page to grab the newest method/version: https://developer.1password.com/docs/cli/get-started/ (use it to confirm the latest Linux install steps + latest CLI version).


Scene 1 — “Meet your courier”: install the 1Password CLI (op) on your server

Option A (recommended): install a pinned version from 1Password’s CDN (copy/paste)

This is the most deterministic way (no guessing, no distro repo surprises).

# 0) Prereqs
sudo apt-get update
sudo apt-get install -y curl unzip

# 1) Pick a version (update this later if needed)
#
OP_VERSION="2.30.3"

# 2) Download + install
curl -sSLo /tmp/op.zip "https://cache.agilebits.com/dist/1P/op2/pkg/v${OP_VERSION}/op_linux_amd64_v${OP_VERSION}.zip"
unzip -o /tmp/op.zip -d /tmp/op
sudo install -m 0755 /tmp/op/op /usr/local/bin/op

# 3) Verify
op --version

If you’re on ARM (rare on DO, but possible), swap amd64 → arm64 in the URL.

Option B: use the official instructions (best when your OS is unusual)

Open: https://developer.1password.com/docs/cli/get-started/
What to do with that link: scroll to Linux → choose your install method → follow exactly.

#

Scene 2 — “Give OpenClaw the key”: set OP_SERVICE_ACCOUNT_TOKEN (two crystal-clear options)

You have two valid setups:

Option 1 — Quick test (good for setup, NOT durable)

This sets the token only for your current SSH session.

# Paste this, then paste your token when prompted (it won't echo)
read -s OP_SERVICE_ACCOUNT_TOKEN
export OP_SERVICE_ACCOUNT_TOKEN
echo
echo "Token loaded into this shell session."
#

Now op commands in this session will authenticate as the service account.

Option 2 — Persistent (recommended on a server): add token to the systemd service

This makes the token available to the OpenClaw Gateway service even after reboot.

2.1) Find your OpenClaw service name (no guessing)

systemctl list-units --type=service | grep -E 'openclaw-gateway'

You’ll usually see something like:

  • openclaw-gateway.service
    (or openclaw-gateway-<profile>.service)

For the commands below, I’ll assume: openclaw-gateway.service
If yours differs, replace it.

#

2.2) Create a root-only env file (safe-ish, keeps token out of shell history)

sudo mkdir -p /etc/openclaw
sudo install -m 600 /dev/null /etc/openclaw/op.env
sudo nano /etc/openclaw/op.env

In the editor, paste exactly this (with your real token):

OP_SERVICE_ACCOUNT_TOKEN=op_sa_XXXXXXXXXXXXXXXXXXXXXXXX

Save + exit.

2.3) Tell systemd to load that env file

#
sudo systemctl edit openclaw-gateway.service

Paste this drop-in:

[Service]
EnvironmentFile=/etc/openclaw/op.env

Save + exit, then restart:

sudo systemctl daemon-reload
sudo systemctl restart openclaw-gateway.service
sudo systemctl status openclaw-gateway.service --no-pager
#

Scene 3 — “Bury the treasure”: create the vault item you want OpenClaw to read (clear + explicit)

In 1Password, create:

  • Vault: OpenClaw Automation
  • Item: OpenAI API Key
  • Field that holds the key: usually password

That gives you a stable secret reference path:

op://OpenClaw Automation/OpenAI API Key/password

Don’t “guess” if the field is called password

#

If you’re not sure what the field name is, list item details (this does not print the secret by default):

op item get "OpenAI API Key" --vault "OpenClaw Automation"

Look for the field label you want to read (commonly password).


Scene 4 — “Prove the map works”: test the secret read on the server (without printing it)

This checks access without dumping the secret to your terminal:

op read "op://OpenClaw Automation/OpenAI API Key/password" >/dev/null && echo "OK: secret is readable"
#

If this fails, 2 common fixes:

  • Your OpenClaw process doesn’t have OP_SERVICE_ACCOUNT_TOKEN (use Scene 2, Option 2)
  • You accidentally have 1Password Connect env vars set (1Password says they can take precedence). Check:
env | grep -E '^OP_CONNECT_' || true

If you want, paste what you’re using as the OpenClaw service name from systemctl list-units ... and I’ll rewrite the commands with the exact service string so it’s 100% copy/paste for your droplet.

#

Substack doesn’t have a great “upload this Markdown file and publish” flow (and there isn’t a stable, official public API you can rely on for fully automated publishing), so the realistic goal is:

  • one clean transfer
  • zero manual reformatting after paste

These are the two workflows that actually work in practice:


Option A (simplest, zero drama): Google Docs → paste into Substack (format survives)

Why this works: Substack’s editor preserves rich-text structure really well when the source is Google Docs.

Do this:

  1. Paste the article into Google Docs.
  2. Apply styles:
  • Title = Title
#
  • Section headers = Heading 2
  • Subsections = Heading 3
  • Lists as normal bullets/numbered lists
  1. In Google Docs: Ctrl/Cmd + A → Ctrl/Cmd + C
  2. In Substack: New post → click into editor → Ctrl/Cmd + V

What usually survives perfectly: headings, bold/italics, links, lists, blockquotes.
What you may still adjust: code blocks (sometimes Substack turns them into normal text).

If your post has code blocks, see Option B.


Option B (best if your “source of truth” is Markdown): Markdown → HTML → paste into Substack

Why this works: Substack doesn’t “speak Markdown” reliably, but it does accept pasted HTML/rich content nicely.

#

Step 1) Convert Markdown to HTML (one command)

On your computer (or wherever the markdown lives), install pandoc:

  • macOS (Homebrew):
brew install pandoc
  • Ubuntu/Debian:
sudo apt-get update && sudo apt-get install -y pandoc

Convert:

pandoc article.md -f markdown -t html -o article.html
#

Step 2) Paste the rendered HTML into Substack (no reformatting)

  1. Open article.html in a browser.
  2. Select all (Ctrl/Cmd + A) → copy.
  3. Paste into Substack editor.

Notes:

  • Headings/lists/links generally paste clean.
  • Images won’t “upload themselves” from local HTML—you’ll still insert/upload images in Substack.
  • Code blocks: if you care about them, do a quick check after paste. If Substack flattens them, use Substack’s “code block” formatting on those sections only.

If you truly want “no copy/paste at all”

Then you’re in automation territory (browser automation / unofficial scripts). It’s doable, but brittle (Substack UI changes, auth/cookies, editor quirks). If you tell me what you mean by “avoid copy-pasting”:

  • “I’m fine pasting once, I just don’t want reformatting” → Option A/B above
  • “I want to push from GitHub/Markdown and it shows up as a draft automatically” → tell me your writing setup (Obsidian? Notion? Google Docs? Git repo?) and I’ll suggest the least-brittle pipeline
#

Which one are you aiming for?

#

Stop copy‑pasting API keys: plug 1Password into OpenClaw (Service Account + op read)

A friend of mine (let’s call her Sam) spun up OpenClaw on a fresh DigitalOcean droplet. Ten minutes later, everything worked… except the part where she had to paste API keys into config files on a remote server.

It felt normal. It was also the exact moment the “security debt” started compounding.

This guide is the pattern that fixes it without turning your setup into enterprise cosplay:

  • Create a dedicated 1Password vault for automation
  • Use a 1Password Service Account (not your personal account)
  • Let OpenClaw fetch secrets at runtime using the 1Password CLI (op)

No extra servers. No “secret manager migration project”. Just a clean, rotatable setup.


The one rule that makes this safe-ish

#

Don’t use your personal 1Password vault for automation.
Instead, create a dedicated vault like OpenClaw Automation and pretend:

“Everything in this vault has a higher-than-normal chance of leaking.”

That sounds dramatic, but it’s the right default when you’re running automation on a server with logs, shells, config files, and (potentially) agents. So only put credentials there that you can rotate quickly and can live without for a bit.

That’s why we use a service account + least privilege.


What you’ll end up with (simple mental model)

OpenClaw will not store your API key in openclaw.json.

Instead it will run a command like:

#
op read "op://OpenClaw Automation/OpenAI API Key/password"

…and use the output as the API key at runtime.


Step-by-step playbook (beginner-friendly, copy/paste)

Chapter 1 — “Build the vault”: create a dedicated automation vault

In 1Password:

  1. Create a vault named: OpenClaw Automation
  2. Add an item, for example:
  • Item name: OpenAI API Key
#
  • Put the key in the password field (recommended)

You’re doing this so your secret reference path is stable:

op://OpenClaw Automation/OpenAI API Key/password

Chapter 2 — “Hire a robot”: create a 1Password Service Account (least privilege)

Create a Service Account in 1Password and grant it access to only the OpenClaw Automation vault.

During creation you’ll get a service account token. Treat it like a password.

(You can optionally keep the official docs handy, but you won’t need them to follow this guide.)

#

Chapter 3 — “Give your server a mouth”: install the 1Password CLI (op)

These install commands work as of 2026‑03‑01 on Ubuntu/Debian (typical DigitalOcean droplets).
For the latest official instructions (if anything changes), use:
https://developer.1password.com/docs/cli/get-started/ (use it to confirm the newest Linux install steps/version)

Install op (pinned, deterministic)

# Prereqs
sudo apt-get update
sudo apt-get install -y curl unzip

# Pick a version (you can bump later)
#
OP_VERSION="2.30.3"

# Download + install
curl -sSLo /tmp/op.zip "https://cache.agilebits.com/dist/1P/op2/pkg/v${OP_VERSION}/op_linux_amd64_v${OP_VERSION}.zip"
unzip -o /tmp/op.zip -d /tmp/op
sudo install -m 0755 /tmp/op/op /usr/local/bin/op

# Verify
op --version

Chapter 4 — “Give OpenClaw the key”: set OP_SERVICE_ACCOUNT_TOKEN

You have two valid paths. Pick one.

#

Option A (fastest): set token just for your current SSH session

Good for testing. Not durable across reboots.

# Paste this, then paste your token when prompted (it won't echo)
read -s OP_SERVICE_ACCOUNT_TOKEN
export OP_SERVICE_ACCOUNT_TOKEN
echo
echo "OK: token loaded into this SSH session."

Option B (recommended on servers): set token for the OpenClaw systemd service (persistent)

1) Find your OpenClaw gateway service name

systemctl list-units --type=service | grep -E 'openclaw-gateway'
#

Most commonly it’s:

  • openclaw-gateway.service

If yours is different, substitute the name below.

2) Create a root-only env file

sudo mkdir -p /etc/openclaw
sudo install -m 600 /dev/null /etc/openclaw/op.env
sudo nano /etc/openclaw/op.env

Paste this line (with your real token):

OP_SERVICE_ACCOUNT_TOKEN=op_sa_XXXXXXXXXXXXXXXXXXXXXXXX
#

Save and exit.

3) Attach that env file to the OpenClaw service

sudo systemctl edit openclaw-gateway.service

Paste:

[Service]
EnvironmentFile=/etc/openclaw/op.env

Save and exit.

4) Restart OpenClaw

#
sudo systemctl daemon-reload
sudo systemctl restart openclaw-gateway.service
sudo systemctl status openclaw-gateway.service --no-pager

Chapter 5 — “Prove it works”: test op read from the server (without printing the secret)

This tests access without dumping the secret into your terminal:

op read "op://OpenClaw Automation/OpenAI API Key/password" >/dev/null && echo "OK: secret is readable"

If it fails, check these two things first

#

1) Are you accidentally using 1Password Connect env vars?

1Password Connect env vars can take precedence over the service account token.

Check:

env | grep -E '^OP_CONNECT_' || true

If you see them and you’re not using Connect, remove them from your environment/service configuration and retry.

2) Does the service account actually have access to that vault?

Double-check the service account’s vault access in 1Password.


Chapter 6 — “Teach OpenClaw to ask 1Password”: configure Secrets Management (exec provider)

#

Now the fun part: tell OpenClaw to fetch the API key by running op.

1) Find the absolute path to op

command -v op

If you installed with the commands above, it should be:

  • /usr/local/bin/op

2) Edit your OpenClaw config (openclaw.json)

Add a secrets exec provider + point your model provider’s apiKey to it.

Example (JSON5 style):

#
{
secrets: {
providers: {
onepassword_openai: {
source: "exec",
command: "/usr/local/bin/op",
args: ["read", "op://OpenClaw Automation/OpenAI API Key/password"],

// Important: pass the service token into the exec provider’s environment
passEnv: ["HOME", "OP_SERVICE_ACCOUNT_TOKEN"],

// op prints plain text; not JSON
jsonOnly: false,
},
},
#
},

models: {
providers: {
openai: {
baseUrl: "https://api.openai.com/v1",
models: [{ id: "gpt-5", name: "gpt-5" }],

// This tells OpenClaw: resolve the apiKey by running the provider above
apiKey: { source: "exec", provider: "onepassword_openai", id: "value" },
},
},
},
}
#

This configuration pattern comes from OpenClaw’s Secrets docs (with minor Linux-friendly tweaks):
https://docs.openclaw.ai/gateway/secrets (optional reference; use it if you want to understand every knob)


Chapter 7 — “Restart and verify”: make sure OpenClaw can actually use it

Restart the gateway (if you’re using systemd):

sudo systemctl restart openclaw-gateway.service
sudo systemctl status openclaw-gateway.service --no-pager

Then trigger a simple model call in OpenClaw.

If secrets resolution fails, it will usually fail loudly (which is good: you find out immediately).

#

What you gained (the real reason this is worth doing)

  • Your API key is no longer sitting in a config file on a server
  • Rotation is now a “change vault value” operation, not a “hunt down where this key got pasted” operation
  • Your automation is tied to a service account, not your personal 1Password identity
  • You can split environments cleanly (create OpenClaw Automation - Staging / Prod vaults later)

Quick FAQ (the stuff that trips people)

“op works in my SSH session, but OpenClaw can’t read secrets”

Your SSH shell has the token, but the systemd service doesn’t. Use Option B in Chapter 4.

“Where should I store the service account token?”

#

On servers, the most practical compromise is a root-only env file (/etc/openclaw/op.env) + systemd drop-in. Keep permissions tight (chmod 600) and rotate if exposed.

“Can I use my personal/private vault?”

Service accounts can’t access personal/private/default vaults. Use a dedicated shared vault like OpenClaw Automation.


If you want, tell me:

  1. which provider(s) you’re using (OpenAI only? Anthropic? OpenRouter?), and
  2. whether you want staging vs prod separation,
    and I’ll adjust the config snippet + the op://... paths so you can paste it as-is.
deep thistleBOT
#

Two likely causes here, and yes one of them can be “your OpenClaw is too old”.

0) First: what version are you on?

On the droplet, run and paste:

openclaw --version

secrets.providers is a newer config area. If your DigitalOcean 1‑click image is behind, the Dashboard will reject secrets: { ... } as an unknown/invalid config key.


1) Your snippet might not be valid as a standalone config edit

What you pasted starts with:

#
"secrets": { ... },

That’s only valid inside a surrounding { ... } object. If you pasted just that block into the Dashboard editor (replacing the whole config), it will be invalid.

Try it as a complete object (and remove the trailing comma at the end if the UI is being strict):

{
secrets: {
providers: {
onepassword_nvidia_key: {
source: "exec",
command: "/home/openclaw/.local/bin/op",
args: ["read", "op://Shared with Yug/NVIDIA_API_KEY/password"],
#
passEnv: ["HOME", "OP_SERVICE_ACCOUNT_TOKEN"],
jsonOnly: false,
},
},
},
}

(Full docs: https://docs.openclaw.ai/gateway/secrets)


2) command might be a symlink (and secrets exec provider rejects symlinks by default)

OpenClaw’s exec-secret provider requires command to be a regular file unless you explicitly allow symlinks.

Check:

#
ls -l /home/openclaw/.local/bin/op

If it shows op -> ... (symlink), either:

  • point command at the real file path, or
  • add:
allowSymlinkCommand: true,
trustedDirs: ["/home/openclaw/.local"],

Docs note: by default symlink command paths are rejected (see config reference: https://docs.openclaw.ai/gateway/configuration-reference#secrets)


#

Fastest way to get the exact reason

When you hit “Save” in Dashboard, the Gateway usually logs the validation error. On the droplet:

openclaw logs --follow

Then try saving again and paste the first error line about config validation.

If you paste:

  1. openclaw --version
  2. the exact Dashboard “invalid config” message (or the log line)

…I can tell you which of the above it is in one shot.

#

That error explains the whole thing: your config currently contains an env-var substitution like:

apiKey: "${NVIDIA_API_KEY}"

…and ${…} substitutions are validated at config load. If the env var is missing/empty, OpenClaw refuses to load the config (and the Dashboard will call it “invalid config” too).

So it’s not your 1Password block that’s “wrong” yet — OpenClaw can’t even get past the missing NVIDIA_API_KEY.

Why root “works” but openclaw doesn’t

  • Your gateway/config lives under /home/openclaw/.openclaw/openclaw.json
  • Running CLI as openclaw loads that config → hits ${NVIDIA_API_KEY} → fails
  • Running as root is looking at a different home/config (or no config), so it doesn’t hit that substitution.

Fix (recommended): switch the model apiKey to a SecretRef (no ${NVIDIA_API_KEY} at all)

Update the NVIDIA provider section to use the exec secret provider:

#
{
secrets: {
providers: {
onepassword_nvidia_key: {
source: "exec",
command: "/home/openclaw/.local/bin/op",
args: ["read", "op://Shared with Yug/NVIDIA_API_KEY/password"],
passEnv: ["HOME", "OP_SERVICE_ACCOUNT_TOKEN"],
jsonOnly: false,
},
},
},

models: {
providers: {
#
"custom-integrate-api-nvidia-com": {
// replace the old "${NVIDIA_API_KEY}" with this:
apiKey: { source: "exec", provider: "onepassword_nvidia_key", id: "value" },
},
},
},
}

Once you remove ${NVIDIA_API_KEY}, the config can validate without needing that env var at all.

“Unblock me right now” workaround (temporary)

If you can’t edit the config easily because the Dashboard refuses to save, you can temporarily define the env var just to get past validation:

#
sudo -u openclaw bash -lc 'echo NVIDIA_API_KEY=tmp > /home/openclaw/.openclaw/.env'

Then reload/restart, save your new config (switch to SecretRef), and remove that .env line afterward.

Docs for env loading precedence (why ~/.openclaw/.env works): https://docs.openclaw.ai/help/environment

About your version (2026.2.12)

It’s pretty old, and DO 1‑click images often lag. Even if this specific error is config-related, I’d still upgrade soon.

Official update doc (safe path): https://docs.openclaw.ai/install/updating
Most common upgrade command path is re-running the installer:

curl -fsSL https://openclaw.ai/install.sh | bash
#

One thing to paste that will make this 100% certain

Paste the line from your openclaw.json where models.providers.custom-integrate-api-nvidia-com.apiKey is currently set — I’ll tell you exactly what to replace it with for your setup.

deep thistleBOT
#

That error is not “can’t find the config” — it’s “found the config, but config load aborts because ${NVIDIA_API_KEY} is missing”.

Why Telegram is still working

Because your Gateway process is already running (and likely started at a time when NVIDIA_API_KEY was present in its environment). Your Telegram bot talks to that already-running Gateway.

Meanwhile, when you run openclaw as the openclaw user, the CLI tries to load the config fresh and immediately fails on ${NVIDIA_API_KEY}. CLI failure ≠ Gateway instantly stops.

If the Gateway ever restarts/reloads config without NVIDIA_API_KEY available, then Telegram will break.

Why root behaves differently

Two common reasons:

  1. root may be using a different $HOME → different ~/.openclaw/openclaw.json (or none), so it doesn’t hit that ${NVIDIA_API_KEY} reference.
  2. The systemd service may inject env vars from /opt/openclaw.env, but your interactive openclaw shell session does not.

About /opt/openclaw.env

OpenClaw does not automatically load arbitrary env files like /opt/openclaw.env for CLI runs. By default it loads:

  • .env in the current working directory
#
  • ~/.openclaw/.env

Docs: https://docs.openclaw.ai/help/environment

So yes: it’s totally possible that the service sees NVIDIA_API_KEY (via systemd EnvironmentFile=), but your openclaw user CLI does not.


Fix it (ignore 1Password for now): make NVIDIA_API_KEY available to the openclaw config loader

Step 1) Put the key into ~openclaw/.openclaw/.env

This makes both the Gateway and the openclaw CLI happy (and doesn’t depend on /opt/openclaw.env).

sudo -u openclaw mkdir -p /home/openclaw/.openclaw
sudo -u openclaw nano /home/openclaw/.openclaw/.env
#

Add this line (no export needed):

NVIDIA_API_KEY=YOUR_KEY_HERE

Then lock permissions:

sudo chmod 600 /home/openclaw/.openclaw/.env
sudo chown openclaw:openclaw /home/openclaw/.openclaw/.env

Now re-test:

#
sudo -u openclaw openclaw --version

Step 2) Restart the gateway (so it’s not surviving on “old good env”)

sudo systemctl restart openclaw-gateway.service
sudo systemctl status openclaw-gateway.service --no-pager

If your service name differs, run:

systemctl list-units --type=service | grep -E 'openclaw-gateway'

#

Optional: confirm where the running service currently gets env vars from

This will tell you if systemd is using /opt/openclaw.env:

sudo systemctl show openclaw-gateway.service -p EnvironmentFiles -p Environment

If you paste the output of:

  • systemctl list-units --type=service | grep -E 'openclaw-gateway'
  • sudo systemctl show openclaw-gateway.service -p EnvironmentFiles

…I can tell you the exact service name + whether /opt/openclaw.env is actually wired into the service or if it’s just “a file that exists”.

#

Cool, this helps — you’ve basically discovered you have two separate OpenClaw “worlds” on that box:

  • root world: /root/.openclaw/...
  • openclaw user world (the real one for your droplet service): /home/openclaw/.openclaw/...

And the error is specifically: config contains ${NVIDIA_API_KEY} but when OpenClaw loads config, it can’t see NVIDIA_API_KEY in its environment.


1) About /root/.openclaw/openclaw.json — should you delete it?

Don’t delete yet. It’s not harmful, but it’s confusing because running openclaw ... as root will use that config and make it look like “things work” (or break) differently.

If you want to clean up safely, do a rename (reversible):

sudo mv /root/.openclaw /root/.openclaw.BAK
#

Only do a real delete once you’re 100% sure nothing uses it.


2) Why Telegram “still works” even though openclaw CLI errors

Because your Gateway service is already running and Telegram is talking to that running process.

The CLI (openclaw --version) tries to load config fresh every time, and that fails if the env var is missing. That doesn’t automatically kill the already-running gateway.

But: if the gateway restarts (or you try to save config and it triggers a reload), you’ll hit the same problem.


3) “Key is in /opt/openclaw.env and systemd links it — so why missing?”

Two common causes:

A) The env file format is wrong for systemd

#

EnvironmentFile= expects plain KEY=value lines.

If your file has export NVIDIA_API_KEY=... or spaces like NVIDIA_API_KEY = ..., systemd may ignore it.

Check the exact line:

sudo grep -n 'NVIDIA_API_KEY' /opt/openclaw.env

It should look like exactly:

NVIDIA_API_KEY=xxxxxxxx

(no export, no spaces around =)

#

B) You have more than one gateway service / mismatch between “what you think is running” and “what is running”

Let’s verify what the gateway service really has wired:

sudo systemctl show openclaw-gateway.service -p User -p EnvironmentFiles -p ExecStart

Paste that output.


4) Fast, reliable fix (even if /opt/openclaw.env is being weird)

Put the key in the OpenClaw-supported env location for the openclaw user:

sudo -u openclaw mkdir -p /home/openclaw/.openclaw
#
sudo -u openclaw nano /home/openclaw/.openclaw/.env

Add:

NVIDIA_API_KEY=xxxxxxxx

Then:

sudo chmod 600 /home/openclaw/.openclaw/.env
sudo chown openclaw:openclaw /home/openclaw/.openclaw/.env
#

Now this should stop erroring:

sudo -u openclaw openclaw --version

And then restart gateway so you’re not relying on “old good state”:

sudo systemctl restart openclaw-gateway.service
sudo systemctl status openclaw-gateway.service --no-pager

(Env loading rules reference: https://docs.openclaw.ai/help/environment)


#

Paste these 2 outputs and I’ll tell you exactly what’s going on (service vs env file)

  1. sudo systemctl show openclaw-gateway.service -p User -p EnvironmentFiles -p ExecStart
  2. sudo grep -n 'NVIDIA_API_KEY' /opt/openclaw.env

Once we see those, we’ll know whether /opt/openclaw.env is actually being applied, and whether its formatting is the culprit.

deep thistleBOT
#

You’re basically right about interactive shells: when you su - openclaw / SSH in as openclaw, that session will not automatically load /opt/openclaw.env, so ${NVIDIA_API_KEY} can be “missing” for the CLI even if the service has it.

But that doesn’t explain the Dashboard save failing.

If the Control UI says “invalid config” when you save, that validation is happening in the running Gateway. So the Gateway process itself is almost certainly missing NVIDIA_API_KEY at reload time (or you’re editing a config that still contains ${NVIDIA_API_KEY} but the Gateway can’t see that env var).

The big red flag in your output

This:

sudo systemctl show openclaw-gateway.service -p User -p EnvironmentFiles -p ExecStart
User=

…should also show ExecStart=... and usually EnvironmentFiles=.... Getting basically nothing strongly suggests you’re not looking at the unit that’s actually running the gateway (or it’s a user service / different name / different profile).


#

Let’s find the real gateway service + confirm env wiring (no secrets pasted)

Run these and paste output (they won’t include your key value):

1) List all OpenClaw-related services

sudo systemctl list-units --type=service --all | grep -i openclaw

2) For the service that looks like the gateway, show the important fields

(Replace <UNIT> with the exact service name you see above.)

sudo systemctl show <UNIT> -p Id -p LoadState -p ActiveState -p MainPID -p User -p ExecStart -p EnvironmentFiles -p FragmentPath -p DropInPaths

This will tell us definitively:

  • which user it runs as (root vs openclaw)
#
  • which config/state dir it’s likely using
  • whether /opt/openclaw.env is actually attached via EnvironmentFile=

Why it “used to work” and now saving fails

Most common explanation: the gateway has been running for a while in a “good” state, but config save triggers a reload, and during reload OpenClaw re-validates ${NVIDIA_API_KEY} and now can’t see it → “invalid config”.

This can happen if:

  • the service env file link got lost (service edited/updated)
  • the unit name changed (profile service vs default)
  • /opt/openclaw.env is linked to a different unit than the one actually running
  • gateway is running as root, but your env/key/config live under openclaw (or vice versa)

Quick temporary unblock (so you can at least save config)

#

If you just want the error gone immediately (and you don’t want to touch systemd yet), put the key in the OpenClaw-supported location for the openclaw user:

sudo -u openclaw mkdir -p /home/openclaw/.openclaw
sudo -u openclaw nano /home/openclaw/.openclaw/.env

Add:

NVIDIA_API_KEY=...

That fixes CLI config load for openclaw and often helps the gateway too (if the gateway runs as openclaw and uses the default state dir).

Docs for env loading: https://docs.openclaw.ai/help/environment


#

Paste the outputs from the two systemctl ... commands and we’ll pin down exactly why the env file isn’t being seen by the gateway reload.

lunar acorn
#

Hi! This thread is getting very long and the answers may not be of the highest quality as the context grows. Please create a new thread for new issues 🙂

Thank you!

deep thistleBOT
#

You’re right — this thread has turned into a “multi-issue mega thread” (approvals → 1Password → env/config validation → systemd). The signal-to-noise is dropping.

Please open a new thread (in #help) with a focused title like:

“Control UI says invalid config + MissingEnvVarError for NVIDIA_API_KEY (DO 1‑click, 2026.2.12)”

And include just these bullets (copy/paste template):

  1. openclaw version(s)
  • openclaw --version as root:
  • openclaw --version as openclaw:
  1. Exact error (the MissingEnvVarError block)

  2. Where the gateway config lives

  • Does /home/openclaw/.openclaw/openclaw.json exist? (yes/no)
#
  1. systemd unit details (no secrets)
    Run and paste output:
sudo systemctl list-units --type=service --all | grep -i openclaw

and then (replace <UNIT> with the gateway unit you see):

sudo systemctl show <UNIT> -p Id -p ActiveState -p MainPID -p User -p ExecStart -p EnvironmentFiles -p FragmentPath -p DropInPaths
  1. Env file line format (redact value)
sudo grep -n 'NVIDIA_API_KEY' /opt/openclaw.env

In the new thread, we can solve it cleanly in ~2–3 messages.

#

(And once it’s fixed: please mark the solution so Answer Overflow indexes it for the next person.)

vapid estuary
#

write a blog for auto-approvals

deep thistleBOT
#

Auto‑Approvals in OpenClaw (without turning your server into a free‑for‑all)

You set up an agent. You ask it to do something simple.
And suddenly you’re approving 20 popups just to complete one task.

This post is the “make it practical” guide to Exec Approvals in OpenClaw: what they are, how auto‑approvals actually work, and how to configure them so you get fewer prompts without accidentally granting “run anything on my host”.


The 30‑second version (TL;DR)

  • Exec approvals are the safety interlock for running commands on a real machine (host=gateway or host=node).
  • “Auto‑approve” does not mean “trust all commands”. The safe way is:
  • security: "allowlist"
  • ask: "on-miss" (prompts only when something isn’t allowlisted)
  • Maintain an allowlist of binary paths (not command text)
  • If you truly want “no prompts ever”, that’s ask: "off" — but you should keep security: "allowlist" unless you fully understand the risk.
#

Docs (source of truth):
https://docs.openclaw.ai/tools/exec-approvals


Why approvals exist (the part people skip)

OpenClaw agents often run in a sandbox. That’s good.
But the moment you let an agent run curl, op, git, python, rm, etc. on the host, you’re giving it a lever that can:

  • exfiltrate files
  • modify system state
  • install tooling
  • persist changes

Exec approvals are there so you can say:
“I trust this agent to run these exact executables on this host — and nothing else.”

#

What “auto‑approvals” actually means in OpenClaw

There are three knobs that matter:

1) security (what’s allowed)

  • deny → nothing runs on host
  • allowlist → only allowlisted executables can run
  • full → everything runs (equivalent to elevated / “just trust it”)

2) ask (when to prompt)

  • off → never prompt
  • on-miss → prompt only if not allowlisted (recommended)
  • always → prompt every time

3) Allowlist entries (what you trust)

#

Important: allowlist patterns match the resolved executable path (glob), not “command strings”.

So this works:

  • /usr/bin/curl
  • ~/.local/bin/op
  • /opt/homebrew/bin/rg

And these do not work the way you think:

  • op item create
  • export OP_SERVICE_ACCOUNT_TOKEN=...
  • cat ~/.secrets/foo

Docs line that matters: allowlist “Patterns should resolve to binary paths (basename-only entries are ignored).”


Where approvals live (and why people edit the wrong file)

#

Approvals are enforced on the execution host:

  • If your agent runs exec host=gateway → approvals file is on the gateway machine
  • If your agent runs exec host=node → approvals file is on the node machine

File location on that host:

  • ~/.openclaw/exec-approvals.json

Docs: https://docs.openclaw.ai/tools/exec-approvals


Setup: “prompts only once, then it’s automatic”

Step 1 — Decide your “comfort level”

Recommended default:

#
  • security: "allowlist"
  • ask: "on-miss"
  • askFallback: "deny" (if UI can’t be reached, don’t run random commands)

That means:

  • Known tools run automatically
  • New tools trigger a prompt once
  • You can “Always allow” to grow the allowlist safely

Step 2 — Use the Control UI (best UX)

Go to:

Control UI → Nodes → Exec approvals

Pick the target:

  • Gateway (approvals for gateway-host execution)
  • or your Node (approvals for node-host execution)
#

Then set:

  • security = allowlist
  • ask = on-miss

Add allowlist patterns, then Save.

Docs: https://docs.openclaw.ai/tools/exec-approvals#control-ui-editing

Step 3 — The best way to build your allowlist (low effort)

Run your workflow once.

When prompted, click:

  • Allow once (just this time), or
  • Always allow (adds the resolved executable path to the allowlist)

This is the fastest way to avoid “I typed the wrong path pattern” problems.

#

Example: why one task creates “tons of approvals”

Let’s say the agent runs:

curl ... && unzip ... && mv ... && chmod ... && op item create ...

In allowlist mode, each segment uses a different executable:

  • /usr/bin/curl
  • /usr/bin/unzip
  • /usr/bin/mv
  • /usr/bin/chmod
  • ~/.local/bin/op (or wherever op is installed)
#

If only curl is allowlisted, you’ll get prompted again at unzip, again at mv, etc.

So the fix isn’t “allow curl && unzip ...”.
The fix is “allow the binaries involved”.


CLI workflow (if you prefer terminal over UI)

Docs: https://docs.openclaw.ai/cli/approvals

Common commands:

openclaw approvals get
openclaw approvals get --gateway
#
openclaw approvals get --node <id|name|ip>

Add allowlist entries:

openclaw approvals allowlist add --agent main "/usr/bin/curl"
openclaw approvals allowlist add --agent main "/usr/bin/unzip"
openclaw approvals allowlist add --agent main "/usr/bin/mv"
openclaw approvals allowlist add --agent main "/usr/bin/chmod"
openclaw approvals allowlist add --agent main "~/.local/bin/op"

Replace approvals from a file:

#
openclaw approvals set --file ./exec-approvals.json
openclaw approvals set --gateway --file ./exec-approvals.json
openclaw approvals set --node <id|name|ip> --file ./exec-approvals.json

“Sometimes I don’t see the approval popup” (fix: forward approvals to chat)

If the Control UI prompt is flaky/missed, you can forward approvals to a chat target and approve with /approve.

Docs: https://docs.openclaw.ai/tools/exec-approvals#approval-forwarding-to-chat-channels

Config example:

#
{
approvals: {
exec: {
enabled: true,
mode: "targets", // "session" | "targets" | "both"
agentFilter: ["main"],
targets: [
{ channel: "telegram", to: "123456789" }
]
}
}
}

Then in chat:

#
/approve <id> allow-once
/approve <id> allow-always
/approve <id> deny

This is a huge quality-of-life improvement if you’re not always staring at the dashboard.


Safe bins: the “auto-allow tiny utilities” shortcut (optional)

OpenClaw has tools.exec.safeBins for stdin-only utilities (think jq, head, tail), where it rejects file paths/flags that could turn it into a file reader.

This reduces prompts for harmless pipeline filters.

Key warning from the docs: don’t add interpreters (python3, node, bash, etc.) to safe bins.

#

Docs section: https://docs.openclaw.ai/tools/exec-approvals#safe-bins-stdin-only


Common mistakes (aka “why it still prompts”)

  1. You allowlisted command text, not a binary path
  • “op item create” won’t match; allowlist must match the resolved executable path.
  1. Wrong host
  • You edited gateway approvals, but the agent executes on a node (or vice versa).
  1. Wrong agent scope
  • Allowlists are per-agent (agents.main, etc.). Make sure you’re editing the right agent.
  1. You accidentally routed through a shell
  • Allowlisting /bin/bash or /bin/sh effectively opens the door wide (because bash -lc "anything").
#
  • Prefer allowlisting the actual tool binaries instead.

Final recommendation (sane defaults)

For most people who want “auto approvals” but still want safety:

  • security: "allowlist"
  • ask: "on-miss"
  • Build allowlist by using “Always allow” on first run
  • Forward approvals to chat if you miss UI popups

That gives you: quiet automation + controlled trust.


If you want, tell me: