#exec-approvals.json bad pattern

1 messages · Page 1 of 1 (latest)

warped ferry
#

I'm on version 2028.4.2 ()

When approving a command, instead of seeing the actually command in the pattern, I'm seeing something like this:

    {
      "id": "30b6166a-a7eb-461b-81b9-c213b5c777ac",
      "pattern": "=command:fe3430370de71b71",
      "source": "allow-always",
      "lastUsedAt": 1775316402018
    }

after approving from discord or gateway chat window. Is this a known issue?

torn anvilBOT
#

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

worn lanternBOT
#

Yep — this is expected behavior in recent builds, not a bad write in exec-approvals.json.

"pattern": "=command:fe3430370de71b71" is a durable exact-command fingerprint (hash), used for allow-always in cases where OpenClaw can’t safely persist a clean executable-path pattern.
It’s also intentional for privacy/security so plaintext commands (which may contain secrets) aren’t stored.

Docs:

If you want readable/static entries, add explicit allowlist paths yourself (CLI or Control UI), e.g. openclaw approvals allowlist add "/usr/bin/rg".

If this is happening even for very simple direct commands (no shell wrappers), that may be a regression — worth filing with a repro here: https://github.com/openclaw/openclaw/issues

warped ferry
#

is there a way to see what that command is actually mapped to? or if a hash, probably not.

worn lanternBOT
#

Correct — there’s no built-in reverse mapping for =command:<hash> right now. It’s intentionally one-way.

So:

  • You can’t decode it back from exec-approvals.json.
  • You can only verify a candidate command by hashing the exact normalized command text and comparing.

Example (Linux/macOS):

cmd='echo hi' # must match the approved command text exactly (after trim)
printf '%s' "$cmd" | sha256sum | cut -c1-16
# compare with fe3430370de71b71

OpenClaw stores this form specifically to avoid persisting plaintext command content.

If you want human-readable durable entries, use explicit allowlist patterns instead (openclaw approvals allowlist add ...) rather than command-hash fallback.

warped ferry
#

but if the thing I'm approving is from a cron job that is sending a message, which may be different any time, re-runs will just create a new hash. what is the best way to get around this? I've got it using a node command to send a formatted discord message with something like this:
[12] assistant: Let me try running the send-discord.js script directly:

tool_call: exec
{
"command": "node /home/claw/.openclaw/scripts/send-discord.js --to "147750379999999999" --message "⚾ Phillies Game Today!\n\nPhiladelphia Phillies @ Colorado Rockies\nVenue: Coors Field\nStart Time: 8:10 PM ET\n\n🤖 Generated by: qwen35-35b-a3b""
}
[13] tool: Approval required (id 5d3d3b05, full 5d3d3b05-b99e-4521-9ba0-886e45a4d050).

node is in my allowlist, shouldn't it just map to that?
details (using my baseball agent):
│ gateway │ baseball │ /usr/bin/python3 │ 13m ago │
│ gateway │ baseball │ /usr/bin/echo │ 33m ago │
│ gateway │ baseball │ /usr/bin/node │ 24h ago │
│ gateway │ baseball │ node │ 28m ago │
│ gateway │ baseball │ openclaw │ 24h ago │
│ gateway │ baseball │ ~/.npm-global/bin/openclaw │ 22m ago │
│ gateway │ baseball │ =command:fe3430370de71b71 │ 18m ago │
│ gateway │ baseball │ =command:d2cb54b1f20b849c │ 12m ago │

worn lanternBOT
#

You’re thinking about it correctly — if /usr/bin/node is allowlisted, this should usually skip prompts in ask: on-miss.

What’s happening here is likely: that specific shell command shape (large quoted multiline --message) isn’t being safely reduced to a reusable allowlist pattern, so OpenClaw falls back to exact-command durable hash (=command:...). Since message text changes, hash changes.

Best ways around it:

  1. Best fix: don’t use exec for Discord sends
    Use the message tool directly in the cron agent prompt. Then no exec approval path at all.

  2. If you keep node script flow, keep command text stable
    Don’t inline dynamic message in argv. Pass data via file/stdin/env and keep the command itself constant.

  3. Verify effective policy on the correct host
    openclaw approvals get --gateway
    (or --node <id> if this cron runs on a node host)

If you want, I can help rewrite that cron prompt so it uses message tool directly (cleanest) or so your node call stays stable and reuses approval.

warped ferry
#

yes, show me an example of this using message tool directly, but I switched to a script at some point because I was having issues I believe using message. I have a similar command for sending emails using send-mail.js.

worn lanternBOT
#

Nice — and I think this explains why message felt flaky before.

If your job is --session isolated cron, the recommended path is not “agent calls message tool”.
Cron runner owns delivery (--announce --channel --to) and sends the final text itself.

Recommended (isolated cron)

openclaw cron edit <jobId> \
--session isolated \
--announce \
--channel discord \
--to "channel:147750379999999999" \
--message "Generate today's Phillies update as plain text only. Do not use exec."

Then the agent just returns text; no send-discord.js, no exec approvals.

#

If you still want direct message tool usage (main session / non-isolated)

Use a prompt like:

Build today's Phillies message.
Send it with the message tool (not exec):

{
"action": "send",
"channel": "discord",
"to": "channel:147750379999999999",
"message": "<final formatted text>"
}
warped ferry
#

my cron jobs typically use a prompt like "read the instructions in xyz.md file and follow them" - those instructions may send discord, email or borth. I have the cron job not do any notification (internal). I will update the instructions to use the message tool.

worn lanternBOT
#

Perfect — that’s a solid plan. 👍
If you move Discord sends to message tool, you should see way fewer =command:... approvals.

A good pattern for your xyz.md instructions is:

## Delivery rules
1) Discord messages: use message tool directly (never exec/script for Discord).
2) Email: use send-mail.js (until a native email tool is available).
3) If content is dynamic, avoid embedding it in exec command args when possible (use stdin/file/env).
4) Return a short internal run summary: what was sent, where, and any errors.

And for your cron style (internal / no announce), that fits well with “read instructions and execute.”

If you want, paste your current xyz.md and I can help tighten it so it routes Discord/email cleanly without approval churn.

warped ferry
#

here are the discord related parts:

Overview

Check if the Phillies have a game today and post updates to #baseball channel.
2. ** When sending discord message, use the send-discord.js script:

node /home/claw/.openclaw/scripts/send-discord.js --to "channel:1477503727765164193" --message "[insert discord message]"
  1. If no games found:

    • Send a message to Discord channel #baseball (channel:1477503727765164193) using send-discord.js with:
      • "âšľ No Phillies Game Today"
    • stop processing after posting discord message
  2. If games found:

    • Parse the JSON output to get game details (opponent, venue, start time)

    • Send a message to Discord channel #baseball () using send-discord.js with:

      • "âšľ Phillies Game Today!"
      • Opponent, start time, and venue
    • If status is "Final": post the final score to discord #baseball (channel:1477503727765164193) Then disable this cron:

    • If status is "In Progress" or "Warmup": Post current score to discord channel #baseball (channel:1477503727765164193). A separate score-tracking cron job will handle ongoing updates.

    • If status unavailable: Do nothing.

Delivery

  • When sending discord message, use the send-discord.js script:
    node /home/claw/.openclaw/scripts/send-discord.js --to "channel:1477503727765164193" --message "[insert discord message]"
    
worn lanternBOT
#

Awesome, thanks — yep, this is exactly the section to change.

Here’s a clean drop-in rewrite for the Discord part (no send-discord.js, no exec approvals):

## Delivery (Discord)

When sending to Discord, use the `message` tool directly.

Tool call shape:
{
"action": "send",
"channel": "discord",
"to": "channel:1477503727765164193",
"message": "<final message text>"
}
#

Rules:
- Never use `exec` or `send-discord.js` for Discord delivery.
- Send exactly one Discord message per run unless explicitly told otherwise.
- If delivery fails, include the error in the internal run summary.

And your logic blocks become:

### If no games found
Send:
"âšľ **No Phillies Game Today**"
Then stop processing.
#
### If games found
Send:
"âšľ **Phillies Game Today!**"
+ opponent, venue, start time.

If status is "Final": post final score, then stop processing.
If status is "In Progress" or "Warmup": post current score.
If status unavailable: do nothing.

This should eliminate the rolling =command:<hash> entries for Discord sends.

warped ferry
#

st run seems to be having some trouble. 6] assistant: Great! There's a Phillies game today. Let me format and post the game announcement:

tool_call: message
{
"action": "send",
"channel": "discord",
"topic": "channel:1477503727765164193",
"message": "⚾ Phillies Game Today!\n\nColorado Rockies @ Coors Field\nStart Time: 8:10 PM ET\n\n🤖 Generated by: qwen35-35b-a3b"
}
[7] tool: {
"status": "error",
"tool": "message",
"error": "Action send requires a target."
}
tool_call_id: JZ3YXTOXRmDhrK9WEvkfMy5cEKV0S2sh
[8] assistant: Let me fix the channel parameter:

tool_call: message
{
"action": "send",
"channel": "discord",
"topic": "channel:1477503727765164193",
"message": "⚾ Phillies Game Today!\n\nColorado Rockies @ Coors Field\nStart Time: 8:10 PM ET\n\n🤖 Generated by: qwen35-35b-a3b"
}
[9] tool: {
"status": "error",
"tool": "message",
"error": "Action send requires a target."
}
tool_call_id: fBW254O6G5kcbk6PMy5TNNuHpcB60SMB

worn lanternBOT
#

Yep, that error is just the param name: it needs target (or to), not topic.

Use this exact shape in your instructions:

{
"action": "send",
"channel": "discord",
"target": "channel:1477503727765164193",
"message": "âšľ **Phillies Game Today!**\n\nColorado Rockies @ Coors Field\nStart Time: 8:10 PM ET"
}

topic is ignored for action=send, so it keeps throwing “requires a target.”

If you want, add this guard line to your md:

#
  • For message tool sends, always set target (never topic).
warped ferry
#

[8] assistant: The Phillies have a game today at Coors Field against the Colorado Rockies starting at 8:10 PM ET. The game is currently scheduled (not yet in progress).

Let me post the announcement to Discord and then enable the watch job:

tool_call: message
{
"action": "send",
"channel": "discord",
"target": "channel:1477503727765164193",
"message": "⚾ Phillies Game Today!\n\nColorado Rockies @ Coors Field\nStart Time: 8:10 PM ET\n\n🤖 Generated by: qwen35-35b-a3b"
}
[9] tool: {
"status": "error",
"tool": "message",
"error": "Discord bot token missing for account "1477503727765164193" (set discord.accounts.1477503727765164193.token or DISCORD_BOT_TOKEN for default)."
}
tool_call_id: x3Kh24oTdOEKkHfq6mfvyTwiNIUvyyNr
DISCORD_BOT_TOKEN is set in my .openclaw/.env file - shouldn't this still see it (and it saw it before with send-discord.js)

worn lanternBOT
#

Ah — this one is different from the earlier target error.

You’re now hitting account resolution, not env loading.
DISCORD_BOT_TOKEN is only used for the default Discord account, but this call is trying to use account "1477503727765164193" (your channel id), so it never reaches default-token fallback.

Use this exact payload in your instructions:

{
"action": "send",
"channel": "discord",
"accountId": "default",
"target": "channel:1477503727765164193",
"message": "..."
}