#outh codex

1 messages ¡ Page 1 of 1 (latest)

thorn pendantBOT
#

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

unborn doveBOT
forest zenithBOT
#

Let me verify the auth/model docs so I can give the clean fix, not a guess.

#

You’ve got an auth/model mismatch.

openai/* needs a real OPENAI_API_KEY.
Codex OAuth only works with openai-codex/* (or codex/* if you’re using the Codex harness).

So the fix is one of these:

  1. Using Codex OAuth
    Set your model to:

    agents: { defaults: { model: { primary: "openai-codex/gpt-5.4" } } }
    
  2. Using direct OpenAI API
    Keep openai/gpt-5.4, but set:

void portal
#

why do i have to grantaccces to c=edit config from UI every time can you give access to it by default

forest zenithBOT
#

It should not ask every time.

What’s happening is usually one of these:

  1. Your browser/device identity changes
    Incognito, cleared site data, different browser/profile, or a different URL can look like a new device.

  2. c=edit config is an admin-scope action
    config.* methods are gated as operator.admin, so if the browser only has a lower/stale device token, the UI will trigger another approval/scope-upgrade request.

What to do:

openclaw --version
openclaw devices list
#

Then:

  • find the pending/current browser request
  • approve it with the current requestId:
openclaw devices approve <requestId>

If it keeps happening for the same browser profile, cleanest reset is:

openclaw devices list
openclaw devices remove <deviceId>

Then reconnect the UI and approve the fresh request once.

#

A couple important notes:

  • 127.0.0.1 / localhost should auto-approve
  • Tailnet/LAN requires explicit approval once
  • same browser profile matters, clearing storage or using private mode will re-pair

I would not recommend making it globally trusted with gateway.controlUi.dangerouslyDisableDeviceAuth=true unless this is just a break-glass local box. That disables an important safety check.

Docs:

If you want, send:

  • openclaw --version
  • whether you’re connecting via localhost or Tailnet/LAN
  • whether this is the same browser profile every time
#

and I’ll narrow down which of the two cases you’re hitting.

void portal
#

asking me approval every time

forest zenithBOT
#

Yeah, if it’s asking every single time, that’s not normal.

Most likely causes:

  • using incognito/private mode
  • browser is clearing site data/local storage
  • switching browser/profile
  • switching between localhost / 127.0.0.1 / Tailnet IP / LAN URL

Control UI treats that as a different device/browser identity.

Try this:

  1. use one normal browser profile, not private mode
  2. always open the same URL, ideally local:
    • http://127.0.0.1:18789/
  3. if it’s stuck with a bad/stale pairing, reset it once:
#
   openclaw devices list
   openclaw devices remove <deviceId>

then reconnect and approve the fresh request once

If you’re on localhost/127.0.0.1, it should usually auto-approve.
If you’re on Tailnet/LAN, one-time approval is expected, but not every time.

Send these if you want me to pinpoint it:

openclaw --version
openclaw devices list

and tell me whether you’re opening it via localhost or Tailnet/LAN.

void portal
#

aksing me eveyt time it ahs to exec

forest zenithBOT
#

Ah, got it, this is exec approvals, not the config editor.

If it prompts on every exec, the effective exec policy is probably:

  • ask: "always", or
  • you’re hitting strictInlineEval commands like python -c / node -e

Easiest fix in the UI

Go to:

Control UI → Nodes → Exec approvals

Then for Gateway (or your Node, if exec runs there), change the defaults to either:

  • recommended: security=allowlist, ask=on-miss
  • never prompt / YOLO: security=full, ask=off
#

CLI, if you want to verify first

openclaw approvals get --gateway
openclaw exec-policy show

Local “never prompt” setup

openclaw exec-policy set --host gateway --security full --ask off --ask-fallback full

If your execs run on a node, you need to change that node’s approvals too, not just the gateway:

openclaw approvals get --node <id|name>
void portal
#

ask = always

forest zenithBOT
#

Yep, that’s the reason.

ask=always means prompt on every exec, even if the command is already allowlisted.

If you want fewer prompts, change it to:

  • ask=on-miss → only prompt for commands not on the allowlist
  • ask=off → never prompt

Good default

openclaw exec-policy set --host gateway --security allowlist --ask on-miss --ask-fallback deny

Never prompt

#
openclaw exec-policy set --host gateway --security full --ask off --ask-fallback full

If your execs are running on a node instead of the gateway, change that node’s exec approvals in:

Control UI → Nodes → Exec approvals

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

Glad we found it 😌

void portal
#

/approve a7de4f90 allow-once

If that short one doesn’t work:

/approve a7de4f90-69db-4d31-8849-f55804ffd525 allow-once

That’s the current live approval for fixing the config and restarting OpenClaw.

still askign

forest zenithBOT
#

Yep, that’s expected.

allow-once only approves that one exec.
And with effective ask=always, it will keep prompting on the next exec anyway.

Also important: even allow-always won’t stop prompts while ask=always is active.

So the real fix is to change the policy, not keep approving commands one by one:

Prompt only on new/unallowlisted commands

openclaw exec-policy set --host gateway --security allowlist --ask on-miss --ask-fallback deny

Never prompt

#
openclaw exec-policy set --host gateway --security full --ask off --ask-fallback full

Then verify:

openclaw exec-policy show
openclaw approvals get --gateway

If it still says ask=always after that, the host approvals layer is still stricter than your session/config.

If your exec is running on a node, you need to change the node approvals instead of the gateway one.

void portal
#

discord bot invted syas integraitno require code grant

forest zenithBOT
#

That’s a Discord app setting, not an OpenClaw bug.

If you’re just inviting the bot normally, go to:

Discord Developer Portal → Your App → Bot → Authorization Flow

and turn off:

  • Requires OAuth2 Code Grant

Then save, regenerate/reuse the invite, and invite again.

For a normal bot invite, the OAuth2 URL should usually just include scopes like:

  • bot
  • applications.commands
#

If Requires OAuth2 Code Grant is on, Discord expects a full OAuth2 auth-code flow instead of a normal bot invite.

So, short version: disable that toggle and re-invite.

void portal
#

disabled the toggle still same issue

forest zenithBOT
#

Then it’s probably using a stale/custom invite URL, not the new setting.

Try this exactly:

  1. Discord Developer Portal → OAuth2
  2. Use the OAuth2 URL Generator, do not reuse the old link
  3. Select only:
    • bot
    • applications.commands
  4. Pick the normal bot perms you need, for example:
    • View Channels
    • Send Messages
    • Read Message History
    • Embed Links
    • Attach Files
  5. Copy the new generated URL and open that one
#

Important:

  • avoid extra OAuth scopes like identify, email, guilds.join
  • avoid any custom URL with response_type=code / redirect_uri
  • avoid Administrator unless you really need it

That’s the OpenClaw docs flow too: https://docs.openclaw.ai/channels/discord

If it still throws the same error after generating a fresh URL from the portal, paste the invite URL here and I’ll sanity-check it.

void portal
#

Because in this session I only have local filesystem, shell, web, and OpenClaw session tools, not a first-class Discord API write tool.
So I can:
configure OpenClaw locally
restart and verify the gateway
generate invite links
inspect logs
But I cannot natively do:
create Discord channels
change server settings
manage roles/permissions via Discord API
I could only do Discord-side writes if:
OpenClaw exposed a Discord messaging/admin tool here, or
I used an external script/API path with your approval and credentials
Short version, I can control your OpenClaw box directly, but not your Discord server as an admin client from this toolset.

forest zenithBOT
#

Yeah, you can ignore that message, it’s just the failed agent talking about its own tool limits. Not the root problem.

For your issue, we don’t need Discord admin API access anyway. We just need to verify the invite URL/settings.

Most useful next step:

  • paste the fresh invite URL you generated from OAuth2 URL Generator
  • or send a screenshot of:
    • Bot tab, showing Requires OAuth2 Code Grant = off
    • OAuth2 URL Generator scopes

What I’m looking for is whether the URL still contains stuff like:

  • response_type=code
  • redirect_uri=...
  • extra scopes like identify, email, guilds.join
#

For a normal OpenClaw Discord bot invite, it should basically just be:

  • scope=bot applications.commands

plus permissions.

If you paste the link, I’ll check it fast.

void portal
#

I can help with that, but I can’t create Discord channels from here unless Uday has wired up a Discord admin skill/tool for it.

If you want, tell me:
channel name
text or voice
category
who should have access

and I’ll draft the exact setup or a clean request for Uday/admin.|

i want the capability to doit

forest zenithBOT
#

In your own server, yes, you can wire that up, but it needs two things:

  1. Discord bot permissions
    Give the bot a role with at least:

    • Manage Channels
    • optionally Manage Roles / Manage Webhooks if you want broader admin actions
  2. OpenClaw tool access
    The agent needs the Discord/message admin surface enabled, and you should restrict who can trigger it with your usual allowlists (channels.discord.allowFrom, command allowlists, etc.).

So the short version is:

  • Discord decides what the bot account is allowed to do
  • OpenClaw decides whether the agent is allowed to call those actions

I’d only enable this on a private server you control, not a public/shared one.

#

If you want, I can help you set it up safely for your own Discord server and give you the minimal permission/config checklist.

void portal
#

its my private discord

forest zenithBOT
#

Perfect, then this is a reasonable setup.

For a private Discord, I’d do it like this:

1) Give the bot the Discord perms it needs

At minimum:

  • Manage Channels
  • View Channels
  • Send Messages
  • Read Message History

Optional, depending on what you want:

  • Manage Threads
  • Manage Webhooks
  • Manage Roles (only if you really want role changes too)
#

2) Put your server on the guild allowlist

{
  channels: {
    discord: {
      groupPolicy: "allowlist",
      guilds: {
        YOUR_SERVER_ID: {
          requireMention: false,
          users: ["YOUR_USER_ID"],
        },
      },
    },
  },
}
#

That’s the recommended private-server pattern from the Discord docs:
https://docs.openclaw.ai/channels/discord

3) Keep commands/tool access restricted to you

If you use command allowlists, make sure your Discord user is authorized.

4) If you run restrictive tool allowlists

Make sure the agent still has messaging enabled:

{
  tools: {
    allow: ["group:messaging"]
  }
}
#

Only do that if you’re already using a restrictive tools.allow.

5) If you want config writes from chat too

{
  commands: {
    config: true
  }
}

That enables /config ... commands, but keep access tight.

If you want, I can give you a minimal full openclaw.json snippet for a private Discord where the bot can act across the server and create/manage channels safely.

void portal
#

How ro make sure this bor is not added to pther servers

forest zenithBOT
#

Two layers, do both:

1) Discord side, prevent invites

In Discord Developer Portal → Bot, turn off:

  • Public Bot

That makes it so random people can’t invite it to other servers.

2) OpenClaw side, ignore any other servers anyway

Keep Discord in guild allowlist mode and only list your server:

{
  channels: {
    discord: {
#
      groupPolicy: "allowlist",
      guilds: {
        YOUR_SERVER_ID: {
          requireMention: false,
          users: ["YOUR_USER_ID"],
        },
      },
    },
  },
}

So even if it somehow lands in another server, OpenClaw won’t act there.

Docs: https://docs.openclaw.ai/channels/discord

#

Best practice is:

  • Public Bot = off
  • guild allowlist = only your server

That’s the safe combo.

void portal
#

Restarted LaunchAgent: gui/501/ai.openclaw.gateway
Timed out after 60s waiting for gateway port 18789 to become healthy.
Gateway process is running but port 18789 is still free (startup hang/crash loop or very slow VM startup).
Service runtime: status=running, state=active, pid=92836
Gateway port 18789 status: free.
Gateway restart timed out after 60s waiting for health checks.
Tip: openclaw gateway status --deep
Tip: openclaw doctor
chotumiya@chotus-Mac-mini ~ % openclaw gateway restart

forest zenithBOT
#

That message means the LaunchAgent restarted, but the gateway never actually bound port 18789. So it’s usually one of:

  • invalid/broken config
  • startup crash loop
  • stale LaunchAgent install/path mismatch

First, please run these and paste the output:

openclaw --version
openclaw gateway status --deep
openclaw doctor

Then grab the actual startup error:

#
openclaw logs --follow

If status --deep or doctor suggests the service install is stale, re-write the LaunchAgent and retry:

openclaw gateway install --force
openclaw gateway restart

On macOS, this often turns out to be a stale LaunchAgent path after changing Node/global installs, so the --version + status --deep output matters a lot here.

Docs:

#

Paste those outputs and I’ll help narrow it down fast.

#

Nice, we’ve got the actual root cause now:

The gateway is failing because DISCORD_BOT_TOKEN is missing for the LaunchAgent.

On macOS, launchd usually does not inherit your shell export vars, so this happens a lot.

Fix

Put the token in the gateway’s global env file:

mkdir -p ~/.openclaw
nano ~/.openclaw/.env

Add:

#
DISCORD_BOT_TOKEN=YOUR_REAL_BOT_TOKEN

Save, then restart:

openclaw gateway restart

If you haven’t pointed config at that env var yet

openclaw config set channels.discord.token --ref-provider default --ref-source env --ref-id DISCORD_BOT_TOKEN
openclaw config set channels.discord.enabled true --strict-json
openclaw gateway restart
#

Why this happened

Your config is using a SecretRef for channels.discord.token, but at startup the daemon can’t resolve it, because the env var is missing in the service environment.

So the short version is:

  • shell export DISCORD_BOT_TOKEN=... is not enough for LaunchAgent
  • ~/.openclaw/.env is the right place

Docs:

Also, don’t paste the bot token in Discord. If you already exposed it anywhere public, rotate it first.

void portal
#

should add all future ekys give me a tempalte of instructions

forest zenithBOT
#

Yep, that’s a good default rule.

For a daemon / LaunchAgent install, future API keys and bot tokens should usually go in:

~/.openclaw/.env

Reusable template

1) Put the secret in ~/.openclaw/.env

mkdir -p ~/.openclaw
nano ~/.openclaw/.env

Add lines like:

#
DISCORD_BOT_TOKEN=...
OPENAI_API_KEY=...
ANTHROPIC_API_KEY=...
GOOGLE_API_KEY=...
OPENROUTER_API_KEY=...

Rules:

  • one key per line
  • no export
  • no spaces around =
  • do not paste secrets into Discord

2) Point config at the env var when needed

Pattern:

#
openclaw config set <config.path> --ref-provider default --ref-source env --ref-id <ENV_VAR_NAME>

Example:

openclaw config set channels.discord.token --ref-provider default --ref-source env --ref-id DISCORD_BOT_TOKEN

3) Restart the gateway

openclaw gateway restart

4) Verify

#
openclaw status
openclaw gateway status

Short instruction template you can reuse

If OpenClaw runs as a daemon/LaunchAgent, put new API keys and bot tokens in ~/.openclaw/.env, then reference them from config with an env SecretRef, then restart the gateway. Shell export ... alone is not reliable for launchd/systemd services.

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

Glad you caught this one, it’s a super common macOS gotcha.

void portal
#

Not directly, no.

I can help you define it fast though, just send:
name
text or voice
category
private or public

and I’ll turn that into the exact request.

why cant it setup channels

forest zenithBOT
#

Because setting up channels is a Discord admin action, not just normal chat.

So there are a few gates that all have to be true:

  1. Discord bot permission

    • the bot needs Manage Channels in your server
  2. OpenClaw runtime capability

    • the agent/session needs the Discord message/admin action surface exposed
    • some sessions only have normal chat/send tools, not guild-admin actions
  3. Your policy/authorization

    • the sender still has to be allowed to trigger those actions

So when it says it “can’t set up channels”, it usually means one of those layers is missing, not that Discord itself can never do it.

In your case, the pasted message sounds like that specific agent session didn’t have the Discord admin tool surface wired in, so it could chat but not mutate the server.

#

If you want, I can give you a checklist to make your private Discord bot capable of creating channels, end to end.

void portal
#

yes

forest zenithBOT
#

Cool. Here’s the end-to-end checklist to make your private Discord bot able to create channels.

1) Discord app settings

In Discord Developer Portal:

  • Bot

    • Public Bot = off
    • Requires OAuth2 Code Grant = off
  • OAuth2 URL Generator

    • scopes:
      • bot
      • applications.commands
  • Give the bot these permissions:

    • Manage Channels
    • View Channels
#
  • Send Messages
    • Read Message History

Optional:

  • Manage Threads
  • Manage Webhooks
  • Manage Roles (only if you want role changes too)

2) Put the bot token where the daemon can read it

For a LaunchAgent / daemon install:

mkdir -p ~/.openclaw
nano ~/.openclaw/.env
#

Add:

DISCORD_BOT_TOKEN=YOUR_TOKEN

Then point config at it:

openclaw config set channels.discord.token --ref-provider default --ref-source env --ref-id DISCORD_BOT_TOKEN
openclaw config set channels.discord.enabled true --strict-json
openclaw gateway restart

3) Allow your private server + your user

#

Use the guild allowlist pattern:

{
  channels: {
    discord: {
      enabled: true,
      groupPolicy: "allowlist",
      guilds: {
        YOUR_SERVER_ID: {
          requireMention: false,
          users: ["YOUR_USER_ID"],
        },
      },
    },
  },
#
}

That makes it active in your server only.


4) Make sure messaging tools are not blocked

Only if you use restrictive tool allowlists:

{
  tools: {
    allow: ["group:messaging"]
  }
#
}

If you are not using tools.allow, you can usually skip this.


5) Test it

Ask the bot something simple like:

create a text channel named test-lab under category AI

If it still can’t do it, check:

#
openclaw status
openclaw channels status --probe
openclaw logs --follow

6) Common reasons it still fails

Usually one of these:

  • bot missing Manage Channels
  • token not visible to the daemon
  • server/user not on the allowlist
  • tool policy blocks messaging/admin actions
  • the specific session/runtime doesn’t expose Discord admin actions
#

If you want, I can turn this into a single minimal openclaw.json example for your private Discord setup.

void portal
#

already done all these bot has admin permissions

forest zenithBOT
#

Then Discord permissions are not the blocker. Good, that narrows it down.

At that point, it’s usually an OpenClaw tool-policy / session-capability issue:

Check 1, does this session even have the Discord message tool?

In the chat/session where you want it to create channels, run:

/tools verbose

If you don’t see message, that’s the problem.

Check 2, are you using a restrictive tool allowlist?

If you have tools.allow or per-agent tools.allow, make sure it includes:

#
{
  tools: {
    allow: ["group:messaging"]
  }
}

or explicitly message.

Check 3, is this a special agent/runtime?

Some sessions can chat normally but still not expose the full Discord admin action surface.

Check 4, confirm with logs

While asking it to create a channel, run:

#
openclaw logs --follow

If it tries and gets blocked, the logs usually show whether it was:

  • tool denied
  • allowlist/policy blocked
  • missing action/capability in that runtime

If you want, paste:

  • /tools verbose
  • openclaw config get tools
  • openclaw config get agents.list

and I’ll tell you exactly which layer is blocking it.

void portal
#

{
"profile": "coding",
"deny": [
"group:automation",
"sessions_spawn",
"sessions_send"
],
"web": {
"search": {
"enabled": true,
"provider": "brave"
}
},
"elevated": {
"enabled": false
},
"exec": {
"host": "gateway",
"security": "full",
"ask": "off",
"pathPrepend": [
"/Users/chotumiya/.local/bin"
]
},
"fs": {
"workspaceOnly": true
}

forest zenithBOT
#

Yep, there it is.

Your tool profile is coding, and coding does not include group:messaging.

That means the agent can do file/shell/web stuff, but it cannot call the message tool, which is where Discord channel actions live.

Fix

Add group:messaging to the same tools block.

If this is the block you pasted, make it:

{
  "profile": "coding",
  "allow": ["group:messaging"],
#
  "deny": [
    "group:automation",
    "sessions_spawn",
    "sessions_send"
  ],
  "web": {
    "search": {
      "enabled": true,
      "provider": "brave"
    }
  },
  "elevated": {
    "enabled": false
  },
  "exec": {
#
    "host": "gateway",
    "security": "full",
    "ask": "off",
    "pathPrepend": [
      "/Users/chotumiya/.local/bin"
    ]
  },
  "fs": {
    "workspaceOnly": true
  }
}

Then restart:

#
openclaw gateway restart

Why

  • coding profile includes fs/runtime/web/sessions/memory
  • message lives under group:messaging
  • no group:messaging = no Discord admin/message actions

So yeah, this one is tool policy, not Discord permissions. Nice catch.

void portal
#

Eureka); awaiting gateway readiness
06:48:21+00:00 error Cannot access rawData on partial Channel. Use fetch() to populate data.
06:48:21+00:00 error Error: Cannot access rawData on partial Channel. Use fetch() to populate data.
at GuildTextChannel.get rawData [as rawData] (file:///Users/chotumiya/openclaw/dist/extensions/discord/node_modules/@buape/carbon/dist/src/abstracts/BaseChannel.js:37:19)
at GuildTextChannel.get name [as name] (file:///Users/chotumiya/openclaw/dist/extensions/discord/node_modules/@buape/carbon/dist/src/abstracts/BaseGuildChannel.js:11:19)
at dispatchDiscordCommandInteraction (file:///Users/chotumiya/openclaw/dist/extensions/discord/provider-D8OJvlaj.js:2409:61)
at Command.run (file:///Users/chotumiya/openclaw/dist/extensions/discord/provider-D8OJvlaj.js:2366:10)
at processTicksAndRejections (node:internal/process/task_queues:104:5)
at CommandHandler.handleCommandInteraction (file:///Users/chotumiya/openclaw/dist/extensions/discord/node_modules/@buape/carbon/dist/src/internals/CommandHandler.js:119:28)
at Client.handleInteraction (file:///Users/chotumiya/openclaw/dist/extensions/discord/node_modules/@buape/carbon/dist/src/classes/Client.js:303:17)
at InteractionEventListener.handle (file:///Users/chotumiya/openclaw/dist/extensions/discord/node_modules/@buape/carbon/dist/src/plugins/gateway/InteractionEventListener.js:6:9)
at EventQueue.processListener (file:///Users/chotumiya/openclaw/dist/extensions/discord/node_modules/@buape/carbon/dist/src/internals/EventQueue.js:237:13)
at runNext (file:///Users/chotumiya/openclaw/dist/extensions/discord/node_modules/@buape/carbon/dist/src/internals/EventQueue.js:211:17)
Log tail truncated (increase --max-bytes).

forest zenithBOT
#

Ah, this is a real bug, and it’s not a Discord permission issue.

The failure is in the Discord native slash-command path:

  • OpenClaw gets a partial Channel
  • then tries to read channel.name
  • Carbon throws: Cannot access rawData on partial Channel. Use fetch() to populate data.

So the current problem is basically:

native Discord command handler bug on partial channels

Good workaround right now

Disable native Discord commands and use text commands / normal chat instead.

Set:

#
{
  commands: {
    text: true
  },
  channels: {
    discord: {
      commands: {
        native: false
      }
    }
  }
}

Then restart:

#
openclaw gateway restart

After that, use plain chat or text /... commands instead of Discord native slash commands.

Why this helps

This avoids the buggy native interaction handler (dispatchDiscordCommandInteraction) that’s choking on the partial channel object.

Next step

Please also send:

openclaw --version

and file this as a GitHub issue with that stack trace, because this one really does need a code fix:

void portal
#

but still the bot cant creats cahnnels

forest zenithBOT
#

Then I’d check one last very specific thing:

Make sure the Discord channel admin action gate is on

channel-create is controlled by:

channels.discord.actions.channels

Set it explicitly to avoid guessing:

{
  channels: {
    discord: {
      actions: {
#
        channels: true
      }
    }
  }
}

If you use named accounts, also check the account override isn’t turning it off:

channels.discord.accounts.<id>.actions.channels

Why I’m pointing there

The Discord provider does have channel-create support, but it only shows up when the channels action gate is enabled.

#

So at this point the remaining likely causes are:

  1. channels.discord.actions.channels is disabled somewhere
  2. account-scoped config overrides it
  3. you’ve hit another Discord-provider bug in this build

Best next check

Please paste:

openclaw --version
openclaw config get channels.discord.actions
openclaw config get channels.discord.accounts

If channels: true is already set and it still won’t create channels, I’m pretty confident this is a provider bug, not your setup.

void portal
#

zsh: suspended openclaw logs --follow
chotumiya@chotus-Mac-mini ~ % openclaw --version
openclaw config get channels.discord.actions
openclaw config get channels.discord.accounts
OpenClaw 2026.4.19-beta.1 (a56aa6c)

🦞 OpenClaw 2026.4.19-beta.1 (a56aa6c) — Hot reload for config, cold sweat for deploys.

Config path not found: channels.discord.actions

🦞 OpenClaw 2026.4.19-beta.1 (a56aa6c) — I run on caffeine, JSON5, and the audacity of "it worked on my machine."

Config path not found: channels.discord.accounts
chotumiya@chotus-Mac-mini ~ %

forest zenithBOT
#

That actually helps a lot.

Config path not found here just means those keys are unset, not invalid.
And for Discord, the channels action gate defaults to enabled.

So, with:

  • bot has admin
  • messaging tool enabled
  • action gate not explicitly disabled
  • earlier stack trace from dispatchDiscordCommandInteraction

…I don’t think this is your config anymore. I think you’ve hit a Discord provider bug in 2026.4.19-beta.1.

Also, this line:

#
zsh: suspended  openclaw logs --follow

just means you accidentally backgrounded it with Ctrl+Z. Use fg to resume it, or rerun it and stop with Ctrl+C instead.

My recommendation now

I’d stop tweaking permissions and file a GitHub issue with:

  • openclaw --version
  • the partial-channel stack trace
  • note that the bot has Admin
  • note that you enabled messaging / added group:messaging
  • note whether the failure happens via:
    • native slash commands
    • plain chat request
    • both
#

Yep, that config looks fine.

Nothing there explains “can’t create channels”:

  • Discord enabled, good
  • token ref is correct, good
  • your user is allowlisted, good
  • your guild is allowlisted, good
  • no actions.channels: false, so defaults should apply

So this pretty strongly points to a provider/runtime bug, not your Discord config.

Ready-to-paste GitHub issue

Title
Discord bot cannot create channels, native command path crashes on partial channel (2026.4.19-beta.1)

#

Body

### Version
OpenClaw 2026.4.19-beta.1 (a56aa6c)

### What I expected
My Discord bot should be able to create channels in my private server.

### What happened
The bot has admin permissions and Discord is configured correctly, but it still cannot create channels.

I also hit this error in logs from the native command path:

```txt
Cannot access rawData on partial Channel. Use fetch() to populate data.
Error: Cannot access rawData on partial Channel. Use fetch() to populate data.
    at GuildTextChannel.get rawData [as rawData] (...)
#

at GuildTextChannel.get name [as name] (...)
at dispatchDiscordCommandInteraction (...)


### Discord config (token redacted)
```json5
{
  channels: {
    discord: {
      enabled: true,
      token: { source: "env", provider: "default", id: "DISCORD_BOT_TOKEN" },
      groupPolicy: "allowlist",
      dmPolicy: "allowlist",
      allowFrom: ["690486912560922634"],
      guilds: {
        "1494879742501851257": {
          requireMention: false,
#

users: ["690486912560922634"]
}
}
}
}
}


### Notes
- Bot has Administrator in the target server
- `group:messaging` was enabled for the agent/tool profile
- `channels.discord.actions` is not explicitly set
- `channels.discord.accounts` is not explicitly set
- This appears to be a Discord provider/runtime issue rather than a permissions/config issue

If you file it, include the full stack trace too. That should give maintainers enough to reproduce.

void portal
#

dont think itsissue config is wrong some where

forest zenithBOT
#

Fair, and you might be right that there’s another config layer, just not in the Discord block you pasted.

The places I’d check next are:

openclaw config get tools
openclaw config get agents.list
openclaw config get channels.discord.commands

Why those:

  • tools → this is where profile: "coding" / allow: ["group:messaging"] lives
  • agents.list → a per-agent override can still block messaging/admin actions
  • channels.discord.commands → native/text command behavior lives there

So I’d reframe it like this:

#
  • your Discord channel config looks okay
  • if config is still the culprit, it’s probably in tool policy or per-agent overrides

Paste those three outputs and I’ll check the hidden layers with you.

#

Yeah, with that output, I’m with you now, this doesn’t look like a config mistake.

What we’ve ruled out:

  • tools is fine, group:messaging is enabled
  • no agents.list override
  • no channels.discord.commands override
  • your Discord block looked sane
  • bot has admin
  • and we already saw a real stack trace in the Discord provider

So at this point I’d say:

this is almost certainly a bug in 2026.4.19-beta.1, not a bad config

Specifically, it looks like the Discord runtime is unhappy around:

  • native command interactions with partial channels
#
  • and possibly the channel-create action path in this beta

So I wouldn’t keep chasing config ghosts here.

Best next move

File the bug with:

  • version: OpenClaw 2026.4.19-beta.1 (a56aa6c)
  • your Discord config snippet
  • your tools snippet
  • the partial-channel stack trace
  • note that bot has admin and messaging is enabled

If you want, I can make the issue report even tighter and shorter for maintainers.

void portal
#

having isseus with discor dbot Appreciate it, but in a shared Discord channel I’m not going to treat “permission” as enough to start pulling in and executing repo code against Uday’s setup.

Safe version:
I can review the repo and explain what it does
I can prepare an install/adaptation plan
I can help once Uday asks me in the main session

That’s me being careful, not difficult.asking permission only from ui

forest zenithBOT
#

Got it, if you want approvals only in the UI, disable Discord as an approval client.

Use:

{
  channels: {
    discord: {
      execApprovals: {
        enabled: false
      }
    }
  }
}

Then restart:

#
openclaw gateway restart

Important

If you also configured global chat forwarding for approvals under approvals.exec, disable that too, otherwise approvals can still get sent to chat targets.

So the UI-only setup is basically:

  • channels.discord.execApprovals.enabled: false
  • no Discord targets in approvals.exec

Then approvals stay in:

  • Control UI
  • macOS app
  • terminal UI
#

If you want, I can give you the exact openclaw config set ... command for this.

void portal
#

no i want to run from discord chat but its not allowing

forest zenithBOT
#

Ah, got it, then this is not an exec-approval problem.

You’re hitting two different layers:

  1. UI approvals
    These only approve shell/tool execution.

  2. The agent’s own safety policy
    The “shared Discord channel, I won’t do that” message is coming from that, so UI approval won’t override it.

So if you want it to run from your private Discord chat, you need to make the agent treat your server + your user as trusted.

Safe way to do that

Use an owner-only/private Discord, then add a rule in your agent instructions / AGENTS.md like:

#
In my private Discord server(s), requests from my Discord user ID are trusted owner requests.
You may perform normal repo/file/system actions there after standard OpenClaw approvals.
Do not treat my private Discord as an untrusted public support channel.

Even better, make it specific:

Trusted Discord guild: <YOUR_GUILD_ID>
Trusted Discord user: <YOUR_USER_ID>
Allow normal operator actions in that guild for that user, subject to exec approvals.

Important distinction

  • UI approval = “you may execute this command”
  • agent policy = “am I willing to do this in this chat at all?”
#

Right now, the second one is blocking you.

If you want, I can help you write a tight AGENTS.md snippet so it works in your private Discord without making it dangerously open everywhere.

unborn doveBOT
void portal
#

Any fix for bot to create channels?

forest zenithBOT
#

Short answer: not a clean config-only fix that we’ve found.

Given everything you checked:

  • bot has admin
  • Discord config looks sane
  • messaging tool is enabled
  • no obvious tool/agent overrides
  • and you hit that partial channel stack trace

…I think channel creation is broken in your build (2026.4.19-beta.1).

Practical answer

If you need it working today

Your best options are:

#
  1. update to a newer build (if there is one)
  2. file the bug so it gets fixed upstream
  3. use the bot for everything else, but create channels manually for now

If you’re comfortable patching code locally

The native Discord command path is tripping here:

  • extensions/discord/src/monitor/native-command.ts

It’s touching channel.name on a partial channel object and should fetch or avoid that access first.

But that’s a code fix, not a config tweak.

So yeah, frustratingly, my read is:

No reliable config fix, likely needs a provider patch or newer version.

#

If you want, I can help you write a very small patch plan you can hand to a local coding agent.

void portal
#

That’s a cool idea, and I’m into it.

But same boundary as before: in this shared Discord channel, I’m not going to treat a repo link as authorization to pull code into Uday’s setup and start wiring memory systems.

What I can say here:
yes, I can evaluate whether that repo is useful
yes, I can help adapt it to OpenClaw + Obsidian
yes, a memory graph could be genuinely useful if it’s done cleanly

If Uday asks me in the main session, I can:
inspect the repo
map how it fits this workspace
install or adapt it carefully
document the setup so it’s maintainable

Right now, safest answer is: possible, but not from this channel alone.

forest zenithBOT
#

Yep, this is the smoking gun.

That reply is not coming from Discord permissions, tool policy, or UI approvals.

It’s coming from the agent’s own instructions/persona.
The fact it mentions “Uday” and “main session” means your bot is carrying a prompt/ruleset that still thinks it’s operating in someone else’s shared Discord environment.

So the real fix is

Find and change the instruction file that contains that rule, likely one of:

  • AGENTS.md
  • SOUL.md
  • BOOTSTRAP.md
  • workspace memory/instruction files

Fast way to find it

Run a search like:

#
rg -n "Uday|shared Discord channel|main session|not from this channel alone" ~ ~/.openclaw 2>/dev/null

What to change it to

If this is your private Discord, replace that rule with something like:

Private Discord trust rule:
In my private Discord server(s), requests from my Discord user ID are trusted owner requests.
You may perform normal repo/file/system actions there after normal OpenClaw approvals.
Do not treat my private Discord as an untrusted public support channel.

Even better, make it exact:

#
Trusted Discord guild: <YOUR_GUILD_ID>
Trusted Discord user: <YOUR_USER_ID>
Allow operator actions in that guild for that user, subject to exec approvals.

So yeah, the block right now is basically the bot’s personality/instructions are wrong for your setup. Not Discord itself.

void portal
#

main sessions bot doesnt produce visible output

#

you have my permissions
The previous attempt did not produce a user-visible answer. Continue from the current state and produce the visible answer now. Do not restart from scratch.

#

main sessions bot doesnt produce visible output
you have my permissions
The previous attempt did not produce a user-visible answer. Continue from the current state and produce the visible answer now. Do not restart from scratch.

forest zenithBOT
#

That usually means the session send policy is off.

In the main session/chat, run:

/send on

If you want to clear any weird override instead:

/send inherit

Why

OpenClaw can keep a session alive but not send visible replies if that session’s send policy was turned off earlier.

#

If it still stays silent

Then the next likely causes are:

  • the agent is replying with exact NO_REPLY
  • the agent instructions are telling it to stay silent in that channel
  • you’re talking to a different session than the one you think is “main”

So first try /send on, because that’s the most common fix.

#

Yep, thanks. The likely fix is still:

/send on

Or, if you want to clear any session override:

/send inherit

What’s happening is probably that the main session exists but its send policy is off, so it processes turns without posting visible replies.

If that doesn’t fix it, then the remaining likely causes are:

  • the agent is returning exact NO_REPLY
#
  • its instructions are telling it to stay silent
  • you’re not actually talking to the session you think is main

So first thing: run /send on in that main session/chat.

#

Continuing from where we were: the most likely issue is still that the main session’s send policy is off, so it’s processing turns but not posting replies.

Try this in the main session/chat itself:

/send on

If that still doesn’t make it speak, clear any session override:

/send inherit

Then send a dead-simple test message there:

#
reply with visible text: test

If it’s still silent after that

Then it’s probably one of these:

  • the agent is returning exact NO_REPLY
  • its prompt/instructions tell it to stay silent in that channel
  • you’re talking to a different session than the one that’s actually “main”

So the immediate next move is:

  1. /send on
  2. /send inherit if needed
  3. send reply with visible text: test
#

If you want, paste what happens after that and I’ll narrow it down fast.

void portal
#

openclaw claude cli shows three models but openclaw onboard only shows opus

forest zenithBOT
#

That part is probably expected-ish, with one caveat.

openclaw onboard is not a full live model browser. It’s an opinionated setup flow.

For the Anthropic Claude CLI path, onboarding does two things:

  1. it picks a default Claude CLI model up front, and
  2. it seeds a small curated Claude CLI allowlist for the later picker

So it won’t necessarily mirror everything you see from the runtime/catalog side.

What that means in practice

  • If you’re seeing only Opus as the default during onboarding, that can be normal.
  • After onboarding, you should still be able to switch models via the normal model picker/config flow.

But

If the allowlist/model-selection step during onboarding also shows only one Claude CLI model, then that smells like a bug in your build, because current source seeds multiple Claude CLI refs for that path.

#

Quick checks

Run after onboarding:

openclaw models list --provider anthropic
openclaw config get agents.defaults.models

If models list shows more Claude CLI options but onboarding only exposed one, then the mismatch is in the wizard, not your auth.

Docs:

If you want, paste those two outputs and I’ll tell you whether it’s expected behavior or a real onboarding bug.

void portal
#

🦞 OpenClaw 2026.4.19-beta.1 (a56aa6c) — Half butler, half debugger, full crustacean.

Warning: launchctl stop did not fully stop the service; used bootout fallback and left service unloaded
Stopped LaunchAgent (degraded): gui/501/ai.openclaw.gateway

forest zenithBOT
#

That message means:

  • launchctl stop didn’t cleanly confirm the stop
  • so OpenClaw used launchctl bootout as a fallback
  • the LaunchAgent is now unloaded, which usually means it is stopped successfully, just not via the clean path

So this is more of a macOS launchd weirdness warning than a fatal OpenClaw error.

What to do next

Run:

openclaw gateway status
openclaw status
#

If you want it running again:

openclaw gateway start

Or just:

openclaw gateway restart

Practical read

“Stopped LaunchAgent (degraded)” usually means:

  • stop worked enough
  • service is unloaded
#
  • launchd behaved a little janky

If this keeps happening every time, then it’s worth digging into launchd state, but a one-off like this is usually not the root cause.

Troubleshooting docs: https://docs.openclaw.ai/gateway/troubleshooting

void portal
#

🦞 OpenClaw 2026.4.19-beta.1 (a56aa6c) — Claws out, commit in—let's ship something mildly responsible.

Gateway LaunchAgent was installed but not loaded; re-bootstrapped launchd service.
chotumiya@chotus-Mac-mini ~ %

forest zenithBOT
#

Nice, that’s actually a good sign.

It means OpenClaw saw:

  • the LaunchAgent plist was already installed
  • but launchd had it unloaded
  • so it loaded/bootstrapped it again

So the service manager state is basically repaired.

Next check

openclaw gateway status
openclaw status
#

If those look healthy, then the LaunchAgent side is back in shape.

So the flow you just saw was basically:

  1. stop fell back to bootout
  2. service became unloaded
  3. next start noticed that
  4. OpenClaw re-bootstrapped it

That part is normal recovery, not a new bug.

void portal
#

"channels": {
"discord": {
"enabled": true,
"token": {
"source": "env",
"provider": "default",
"id": "DISCORD_BOT_TOKEN"
},
"groupPolicy": "open",
"guilds": {
"DISCORD_SERVER_ID": {
requireMention: False,
users: ["DISCORD_USER_ID"],
}
},
"dmPolicy": "allowlist",
"dm": {
"enabled": true
}
is this right way of import from ,env for server id

forest zenithBOT
#

Not quite.

Two important gotchas

  1. DISCORD_SERVER_ID will not be read from .env there
    Object keys like:
guilds: {
  "DISCORD_SERVER_ID": { ... }
}

are treated as literal text, not env lookups.

  1. Same for users: ["DISCORD_USER_ID"]
    That is also just a literal string, not env expansion.
#

OpenClaw’s env/SecretRef support works for things like tokens/keys, not arbitrary config keys/IDs like guild-map keys.

Also fix these

  • False → false
  • if you want to allow only one server, use groupPolicy: "allowlist", not "open"
  • dmPolicy: "allowlist" needs an actual allowFrom

Correct shape

{
  channels: {
    discord: {
      enabled: true,
      token: {
        source: "env",
#
        provider: "default",
        id: "DISCORD_BOT_TOKEN",
      },

      groupPolicy: "allowlist",
      guilds: {
        "123456789012345678": {
          requireMention: false,
          users: ["690486912560922634"],
        },
      },

      dmPolicy: "allowlist",
      allowFrom: ["690486912560922634"],
#

If you want, I can rewrite your full Discord block into a clean working JSON5 snippet.

void portal
#

i need to get them from .env

forest zenithBOT
#

Then the important bit is:

OpenClaw will not expand .env values into arbitrary config fields or map keys by itself.

So if you need guild/user IDs from .env, the fix is to template the config before OpenClaw reads it.

Recommended approach

Keep a template file like:

{
  channels: {
    discord: {
      enabled: true,
      token: {
#
        source: "env",
        provider: "default",
        id: "DISCORD_BOT_TOKEN",
      },
      groupPolicy: "allowlist",
      guilds: {
        "${DISCORD_SERVER_ID}": {
          requireMention: false,
          users: ["${DISCORD_USER_ID}"],
        },
      },
      dmPolicy: "allowlist",
      allowFrom: ["${DISCORD_USER_ID}"],
      dm: { enabled: true },
    },
#
  },
}

Then render it before starting OpenClaw, for example with envsubst:

envsubst < openclaw.template.json5 > ~/.openclaw/openclaw.json5

Important macOS note

If you use a LaunchAgent, it does not automatically inherit your shell .env.

So:

  • IDs: best rendered into the config file ahead of time
#
  • bot token: either render it too, or make sure the LaunchAgent/service environment actually has DISCORD_BOT_TOKEN

Short version

  • native OpenClaw env refs work for secrets like tokens
  • guild/user IDs from .env need external templating/preprocessing

If you want, I can give you a copy-paste template + render command for macOS.

void portal
#

yes give me template

#

give me

forest zenithBOT
#

Here’s the macOS LaunchAgent-safe version.

This keeps the bot token out of shell env, and only templates the server/user IDs from .env.

1. Put the bot token in ~/.openclaw/secrets.json

{
  "discord": {
    "botToken": "your_bot_token_here"
  }
}

2. Put only the IDs in .env

#
DISCORD_SERVER_ID=123456789012345678
DISCORD_USER_ID=690486912560922634

3. Create openclaw.template.json5

{
  secrets: {
    providers: {
      filemain: {
        source: "file",
        path: "~/.openclaw/secrets.json",
        mode: "json",
      },
#
    },
    defaults: {
      file: "filemain",
    },
  },

  channels: {
    discord: {
      enabled: true,

      token: {
        source: "file",
        provider: "filemain",
        id: "/discord/botToken",
      },
#

      groupPolicy: "allowlist",
      guilds: {
        "${DISCORD_SERVER_ID}": {
          requireMention: false,
          users: ["${DISCORD_USER_ID}"],
        },
      },

      dmPolicy: "allowlist",
      allowFrom: ["${DISCORD_USER_ID}"],

      dm: {
        enabled: true,
      },
#
    },
  },
}

4. Render it to the real config

set -a
source .env
set +a

envsubst < openclaw.template.json5 > ~/.openclaw/openclaw.json

5. Restart OpenClaw

#
openclaw gateway restart

6. If envsubst is missing

brew install gettext
$(brew --prefix gettext)/bin/envsubst < openclaw.template.json5 > ~/.openclaw/openclaw.json

Why this is better on macOS

launchd does not reliably inherit your shell env, so putting DISCORD_BOT_TOKEN only in .env is fragile.

This version avoids that by:

  • storing the token in ~/.openclaw/secrets.json
void portal
#

give me a prompt so i can send this to my openclaw UI and it can do it for me

forest zenithBOT
#

Yep, paste this into your own OpenClaw UI:

I want you to configure my local OpenClaw Discord setup in a macOS LaunchAgent-safe way.

Goal:
- Read DISCORD_BOT_TOKEN, DISCORD_SERVER_ID, and DISCORD_USER_ID from my local environment or .env if available
- Store the bot token in ~/.openclaw/secrets.json using a file-backed SecretRef setup
- Render the Discord guild/user IDs into ~/.openclaw/openclaw.json from a template
- Restart the gateway
- Verify the config loaded correctly

Requirements:
1. Do not print the raw bot token back to chat.
2. If any required value is missing, stop and tell me exactly which one is missing.
3. Before making changes, inspect the current config and preserve unrelated settings.
#
4. Use this target shape for the Discord config:

{
  channels: {
    discord: {
      enabled: true,
      token: {
        source: "file",
        provider: "filemain",
        id: "/discord/botToken"
      },
      groupPolicy: "allowlist",
      guilds: {
        "<DISCORD_SERVER_ID>": {
          requireMention: false,
#
          users: ["<DISCORD_USER_ID>"]
        }
      },
      dmPolicy: "allowlist",
      allowFrom: ["<DISCORD_USER_ID>"],
      dm: { enabled: true }
    }
  },
  secrets: {
    providers: {
      filemain: {
        source: "file",
        path: "~/.openclaw/secrets.json",
        mode: "json"
      }
#
    },
    defaults: {
      file: "filemain"
    }
  }
}

Execution plan:
- Check openclaw --version
- Inspect current ~/.openclaw/openclaw.json if it exists
- Find the required Discord env values
- Create/update ~/.openclaw/secrets.json
- Create/update ~/.openclaw/openclaw.json without clobbering unrelated config
- Restart OpenClaw with `openclaw gateway restart`
- Run `openclaw gateway status` and `openclaw status`
#
- Summarize what changed and whether Discord is now configured

Please do the work, not just describe it.

If you want, I can also give you a shorter more aggressive version for agents that work better with direct commands.

unborn doveBOT
void portal
#

how do it for future api keys and any other variables

forest zenithBOT
#

Best rule of thumb:

Use two lanes

1. Secrets, API keys, tokens, passwords

Use SecretRefs, ideally file-backed or env-backed.

Good for:

  • DISCORD_BOT_TOKEN
  • OPENAI_API_KEY
  • ANTHROPIC_API_KEY
  • gateway tokens
  • webhook secrets

Best long-term workflow:

#
  • hostnames
  • ports
  • booleans
  • object keys like guilds.<id>

Why: OpenClaw does not do native .env expansion for arbitrary config fields or map keys.


Recommended setup for the future

Keep secrets in:

  • ~/.openclaw/secrets.json

Keep non-secret deploy vars in:

  • .env
#

Keep config as:

  • openclaw.template.json5

Render to:

  • ~/.openclaw/openclaw.json

Example pattern

~/.openclaw/secrets.json

{
  "discord": { "botToken": "..." },
  "providers": {
    "openai": { "apiKey": "..." },
#
    "anthropic": { "apiKey": "..." }
  }
}

openclaw.template.json5

{
  secrets: {
    providers: {
      filemain: {
        source: "file",
        path: "~/.openclaw/secrets.json",
        mode: "json",
      },
#
    },
    defaults: { file: "filemain" },
  },

  channels: {
    discord: {
      token: { source: "file", provider: "filemain", id: "/discord/botToken" },
      guilds: {
        "${DISCORD_SERVER_ID}": {
          users: ["${DISCORD_USER_ID}"],
        },
      },
    },
  },
#
  models: {
    providers: {
      openai: {
        apiKey: { source: "file", provider: "filemain", id: "/providers/openai/apiKey" },
      },
      anthropic: {
        apiKey: { source: "file", provider: "filemain", id: "/providers/anthropic/apiKey" },
      },
    },
  },
}

Short answer

#
  • API keys/tokens → SecretRefs
  • IDs / arbitrary vars / object keys → template render step

That’s the clean pattern that scales.

If you want, I can give you a generic reusable OpenClaw template starter you can keep for all future providers.

void portal
#

give me indetail instrucitons

forest zenithBOT
#

Yep. Here’s the full repeatable setup for macOS so future API keys and IDs are manageable.

Goal

Use:

  • ~/.openclaw/secrets.json for secrets
    (API keys, bot tokens, webhook secrets, gateway tokens)
  • .env for non-secret variables
    (Discord guild IDs, user IDs, channel IDs, hostnames, ports)
  • openclaw.template.json5 as your config template
  • envsubst to render the final config into
    ~/.openclaw/openclaw.json

This avoids the macOS LaunchAgent env problem and scales cleanly.


#

Part 1, install the one helper tool

macOS usually does not ship envsubst.

Install it:

brew install gettext

Use it like this:

$(brew --prefix gettext)/bin/envsubst

If you want, add it to PATH later, but you don’t have to.

#

Part 2, back up your current config

Before changing anything:

mkdir -p ~/.openclaw
cp ~/.openclaw/openclaw.json ~/.openclaw/openclaw.json.backup.$(date +%Y%m%d-%H%M%S) 2>/dev/null || true

That gives you a rollback point.


Part 3, create your secrets file

#

Put only secrets here.

Create ~/.openclaw/secrets.json:

{
  "discord": {
    "botToken": "YOUR_DISCORD_BOT_TOKEN"
  },
  "providers": {
    "openai": {
      "apiKey": "YOUR_OPENAI_API_KEY"
    },
    "anthropic": {
      "apiKey": "YOUR_ANTHROPIC_API_KEY"
    }
#
  },
  "gateway": {
    "token": "YOUR_GATEWAY_TOKEN"
  }
}

You do not need every section immediately. Start small if you want.

Example minimal version:

{
  "discord": {
    "botToken": "YOUR_DISCORD_BOT_TOKEN"
#
  }
}

Part 4, create your .env

Put non-secret config variables here.

Create .env somewhere convenient, for example in your home directory or a config folder:

cat > ~/.openclaw/.env <<'EOF'
DISCORD_SERVER_ID=123456789012345678
#
DISCORD_USER_ID=690486912560922634
EOF

Use .env for things like:

  • DISCORD_SERVER_ID
  • DISCORD_USER_ID
  • DISCORD_CHANNEL_ID
  • PUBLIC_HOSTNAME
  • OPENCLAW_PORT

Do not rely on .env alone for secrets on macOS LaunchAgents.


#

Part 5, create your config template

Create ~/.openclaw/openclaw.template.json5:

{
  secrets: {
    providers: {
      filemain: {
        source: "file",
        path: "~/.openclaw/secrets.json",
        mode: "json",
      },
    },
    defaults: {
      file: "filemain",
#
    },
  },

  channels: {
    discord: {
      enabled: true,

      token: {
        source: "file",
        provider: "filemain",
        id: "/discord/botToken",
      },

      groupPolicy: "allowlist",
      guilds: {
#
        "${DISCORD_SERVER_ID}": {
          requireMention: false,
          users: ["${DISCORD_USER_ID}"],
        },
      },

      dmPolicy: "allowlist",
      allowFrom: ["${DISCORD_USER_ID}"],

      dm: {
        enabled: true,
      },
    },
  },
#
  models: {
    providers: {
      openai: {
        apiKey: {
          source: "file",
          provider: "filemain",
          id: "/providers/openai/apiKey",
        },
      },
      anthropic: {
        apiKey: {
          source: "file",
          provider: "filemain",
          id: "/providers/anthropic/apiKey",
        },
#
      },
    },
  },

  gateway: {
    auth: {
      token: {
        source: "file",
        provider: "filemain",
        id: "/gateway/token",
      },
    },
  },
}
#

Notes:

  • token / apiKey fields can use SecretRef
  • guilds.${DISCORD_SERVER_ID} cannot, so we template it
  • users: ["${DISCORD_USER_ID}"] also gets templated

Part 6, render the real config

Load .env, then render the template:

set -a
source ~/.openclaw/.env
set +a
#

$(brew --prefix gettext)/bin/envsubst \
  < ~/.openclaw/openclaw.template.json5 \
  > ~/.openclaw/openclaw.json

Now ~/.openclaw/openclaw.json contains the final config with real IDs.


Part 7, verify the rendered config

Check the file:

#
cat ~/.openclaw/openclaw.json

Make sure:

  • your Discord guild ID is actually present as the object key
  • your Discord user ID appears in users and allowFrom
  • your token is still a file SecretRef, not blank

Part 8, restart OpenClaw

openclaw gateway restart
#

Then verify:

openclaw gateway status
openclaw status
openclaw secrets audit --check

If the gateway is healthy, you’re good.

If something looks off, tail logs:

openclaw logs --follow

#

Part 9, how to update things later

If you need to change a secret

Edit:

~/.openclaw/secrets.json

Then reload or restart:

openclaw secrets reload

or

#
openclaw gateway restart

Examples:

  • rotate Discord bot token
  • add OpenAI key
  • add Anthropic key
  • rotate gateway token

If you need to change a non-secret variable

Edit:

~/.openclaw/.env
#

Then re-render:

set -a
source ~/.openclaw/.env
set +a

$(brew --prefix gettext)/bin/envsubst \
  < ~/.openclaw/openclaw.template.json5 \
  > ~/.openclaw/openclaw.json

Then restart:

openclaw gateway restart
#

Examples:

  • change Discord server ID
  • change allowed Discord user
  • change channel IDs
  • change hostname/port values you template

Part 10, how to add future API keys

Just add them to ~/.openclaw/secrets.json.

Example:

{
#
  "discord": {
    "botToken": "..."
  },
  "providers": {
    "openai": {
      "apiKey": "..."
    },
    "anthropic": {
      "apiKey": "..."
    },
    "google": {
      "apiKey": "..."
    },
    "firecrawl": {
      "apiKey": "..."
#
    }
  }
}

Then reference them in config with SecretRef.

Example pattern:

{
  source: "file",
  provider: "filemain",
  id: "/providers/google/apiKey",
}
#

Same idea for any supported secret field.


Part 11, what belongs where

Put in secrets.json

Use for:

  • API keys
  • OAuth client secrets
  • bot tokens
  • webhook secrets
  • gateway auth token
  • anything sensitive

Put in .env

Use for:

#
  • server IDs
  • user IDs
  • channel IDs
  • hostnames
  • ports
  • feature flags
  • other non-secret deployment values

Put directly in template

Use for:

  • stable structure
  • policy choices
  • booleans that won’t vary much
  • config layout

Examples:

  • groupPolicy: "allowlist"
#
  • dmPolicy: "allowlist"
  • requireMention: false

Part 12, important limitations

1. SecretRefs are not universal

They work on supported credential surfaces, not every arbitrary field.

Canonical reference:
https://docs.openclaw.ai/reference/secretref-credential-surface

2. Object keys cannot be SecretRefs

This does not work natively:

#
guilds: {
  { source: "env", ... }: {}
}

That’s why templating is needed.

3. LaunchAgent does not reliably inherit shell env

That’s why secrets should not depend on .env alone for daemon runs.


Part 13, best maintenance workflow

My recommendation:

#
  1. edit ~/.openclaw/secrets.json for secrets
  2. edit ~/.openclaw/.env for IDs/variables
  3. keep ~/.openclaw/openclaw.template.json5 as source of truth
  4. render to ~/.openclaw/openclaw.json
  5. run:
    openclaw gateway restart
    openclaw status
    

That’s the cleanest long-term setup.


Part 14, useful commands

Validate current runtime

#
openclaw status
openclaw gateway status

Audit secret setup

openclaw secrets audit --check

Reload secrets without full restart

openclaw secrets reload

Restart after template changes

void portal
#

i dont have openclar.template.jons5 just have openclaw.json

forest zenithBOT
#

That’s totally fine, openclaw.template.json5 is not built in.

It’s just a helper file you create yourself so you can generate openclaw.json.

Easiest path

Use your current config as the starting point:

cp ~/.openclaw/openclaw.json ~/.openclaw/openclaw.template.json5

Then edit the new template file and replace the literal values you want to come from .env, for example:

  • replace your real guild ID with ${DISCORD_SERVER_ID}
  • replace your real user ID with ${DISCORD_USER_ID}
#

Example:

guilds: {
  "${DISCORD_SERVER_ID}": {
    requireMention: false,
    users: ["${DISCORD_USER_ID}"],
  },
},
allowFrom: ["${DISCORD_USER_ID}"],

Then render it back into the real config:

set -a
#
source ~/.openclaw/.env
set +a

$(brew --prefix gettext)/bin/envsubst \
  < ~/.openclaw/openclaw.template.json5 \
  > ~/.openclaw/openclaw.json

Short version

  • openclaw.json = real config OpenClaw reads
  • openclaw.template.json5 = optional file you create for templating

If you want, I can show you exactly how to convert your current openclaw.json into a template.

void portal
#

done everything now

forest zenithBOT
#

Nice 🎉

Now do the final checks:

openclaw gateway restart
openclaw gateway status
openclaw status
openclaw secrets audit --check

Then test in Discord:

  1. DM the bot
  2. message it in your allowed server
  3. make sure it responds only where you expect
#

If something fails, grab:

openclaw logs --follow

and paste the error.

If those checks pass, you’re basically done.

#

Good, this narrows it down.

The actual blocker

This one is the important error:

[REF_UNRESOLVED] ... channels.discord.token ...
Failed to resolve file:filemain:/discord/botToken
(Bad control character in string literal in JSON ...)

That means your ~/.openclaw/secrets.json is not valid JSON.

Most likely causes:

  • pasted token with a raw newline inside the quotes
  • smart quotes instead of normal "
#
  • trailing commas
  • file is JSON5-ish, but mode: "json" requires strict JSON

Fix that first

Your file should look like this exactly:

{
  "discord": {
    "botToken": "YOUR_DISCORD_BOT_TOKEN"
  }
}

Important:

  • normal double quotes only
#
  • no trailing comma
  • one-line token string
  • no comments

Validate it

Run:

python3 -m json.tool ~/.openclaw/secrets.json

If that errors, the file is still malformed.

If it succeeds, restart:

#
openclaw gateway restart
openclaw secrets audit --check

About the other audit findings

1. gateway.auth.token plaintext

Not the Discord blocker, but you should migrate it later.

2. models.json:providers.codex.apiKey plaintext

Also not the Discord blocker. That’s a residue worth cleaning later.

3. profiles.anthropic:claude-cli legacy OAuth

That one is expected-ish and not the thing breaking Discord.

#

My recommendation

Do this in order:

  1. fix ~/.openclaw/secrets.json
  2. validate it with python3 -m json.tool
  3. restart gateway
  4. rerun:
    openclaw secrets audit --check
    

If you want, paste your secrets.json with the token value redacted like:

#
{
  "discord": {
    "botToken": "REDACTED"
  }
}

and I’ll sanity-check the format.

void portal
#

Secrets audit: findings. plaintext=2, unresolved=0, shadowed=0, legacy=1.

  • [PLAINTEXT_FOUND] /Users/chotumiya/.openclaw/openclaw.json:gateway.auth.token gateway.auth.token is stored as plaintext.
  • [LEGACY_RESIDUE] /Users/chotumiya/.openclaw/agents/main/agent/auth-profiles.json:profiles.anthropic:claude-cli OAuth credentials are present (out of scope for static SecretRef migration).
  • [PLAINTEXT_FOUND] /Users/chotumiya/.openclaw/agents/main/agent/models.json:providers.codex.apiKey models.json provider apiKey is stored as plaintext.
forest zenithBOT
#

Much better. The important part is:

unresolved = 0

So your Discord token SecretRef is working now. Nice, we got past the real blocker.

What’s left

1. gateway.auth.token plaintext

This is worth fixing.

Add to ~/.openclaw/secrets.json:

{
  "gateway": {
#
    "token": "YOUR_GATEWAY_TOKEN"
  }
}

Then change openclaw.json to:

gateway: {
  auth: {
    mode: "token",
    token: {
      source: "file",
      provider: "filemain",
      id: "/gateway/token",
#
    },
  },
}

2. profiles.anthropic:claude-cli legacy residue

This is not your Discord problem. It’s basically expected for Claude CLI OAuth and can be ignored unless you specifically want to audit that path harder.

3. models.json:providers.codex.apiKey plaintext

This is also not the Discord blocker. It’s a generated residue/cached model config issue.

My practical recommendation

If your bot is now working, do this:

fix now

  • migrate gateway.auth.token to the same file-backed secret store
#

ignore for now

  • Claude CLI legacy OAuth residue
  • Codex plaintext residue in models.json

Then rerun:

openclaw secrets audit --check

Real status

At this point, the setup is no longer broken, you’re down to cleanup findings, not the main failure.

If you want, I can give you the exact edited gateway.auth block to paste into your config.

unborn doveBOT
void portal
#

i already migrated the gateaway token

forest zenithBOT
#

Then the audit is telling you something useful:

the actual ~/.openclaw/openclaw.json still contains a literal gateway token

So one of these is happening:

  1. you updated the template, but didn’t re-render openclaw.json
  2. you edited the wrong file
  3. there’s still an old gateway.auth.token value sitting in openclaw.json

Quick check

Run:

openclaw config get gateway.auth
#
rg -n "gateway|token" ~/.openclaw/openclaw.json

If audit says:

/Users/chotumiya/.openclaw/openclaw.json:gateway.auth.token is stored as plaintext

then that file still has a plain token in it. Full stop.

What it should look like

gateway: {
#
  auth: {
    mode: "token",
    token: {
      source: "file",
      provider: "filemain",
      id: "/gateway/token",
    },
  },
}

Not:

gateway: {
#
  auth: {
    token: "actual-token-here"
  }
}

Most likely fix

If you’re using templating, re-render the config again, then restart:

set -a
source ~/.openclaw/.env
set +a

$(brew --prefix gettext)/bin/envsubst \
#
  < ~/.openclaw/openclaw.template.json5 \
  > ~/.openclaw/openclaw.json

openclaw gateway restart
openclaw secrets audit --check

Paste openclaw config get gateway.auth if you want, and I’ll tell you exactly what’s still wrong.

void portal
#

i jsut dont want all these can to revert back to intial setu

forest zenithBOT
#

Yeah, totally fair. If you just want it working, you do not need to chase every audit warning right now.

My honest recommendation

If the bot is working now, stop here and leave it alone.

The remaining findings are mostly cleanup/hygiene, not “your setup is broken”.

Safe to ignore for now

  • LEGACY_RESIDUE for Claude CLI
  • models.json plaintext Codex residue

Only care if it actually breaks something

  • unresolved refs
  • gateway not starting
  • Discord not connecting
  • bot not replying where it should
#

So yes, you can basically go back to “initial setup” mode

Meaning:

  • keep using your openclaw.json
  • don’t worry about making audit perfectly clean
  • don’t keep refactoring unless something is actually broken

My suggestion

Do this now:

openclaw gateway restart
openclaw status
#

Then test the bot.

If it works, call it done.

You do not need a perfect secrets migration today.

If you want, I can help you decide the minimum set of things worth keeping versus the stuff you can ignore.

void portal
#

gateway not restarting

forest zenithBOT
#

Okay, let’s get the exact failure instead of guessing.

Please run and paste these:

openclaw gateway status
openclaw status --all
openclaw logs --follow

If logs --follow keeps running, stop it with Ctrl+C , not Ctrl+Z.

Also try restart as two separate steps and paste the output:

openclaw gateway stop
#
openclaw gateway start

If start fails, the most useful thing is the first error line from logs. That’ll tell us whether this is:

  • bad config
  • unresolved secret ref
  • launchd/macOS weirdness
  • auth/token issue

Paste those outputs and I’ll narrow it down.

void portal
#

Secrets audit: findings. plaintext=1, unresolved=0, shadowed=0, legacy=1.

  • [LEGACY_RESIDUE] /Users/chotumiya/.openclaw/agents/main/agent/auth-profiles.json:profiles.anthropic:claude-cli OAuth credentials are present (out of scope for static SecretRef migration).
  • [PLAINTEXT_FOUND] /Users/chotumiya/.openclaw/agents/main/agent/models.json:providers.codex.apiKey models.json provider apiKey is stored as plaintext.
forest zenithBOT
#

That audit is actually good news.

Those two remaining findings are not what would stop the gateway from restarting:

  • LEGACY_RESIDUE for Claude CLI, not a startup blocker
  • models.json providers.codex.apiKey plaintext residue, also not a startup blocker

So the restart problem is somewhere else.

We need the actual gateway error now

Please paste these:

openclaw gateway status
openclaw status --all
openclaw gateway stop
#
openclaw gateway start

And if start fails, also grab the first log error:

openclaw logs --follow

Stop logs with Ctrl+C after the first error appears.

Right now, the secrets audit is mostly cleanup. The restart failure needs status/log output.

void portal
#

just revert back to intial setup

forest zenithBOT
#

Yep, if you want the simple path, I’d undo the templating/secrets stuff and go back to one plain openclaw.json.

Fastest revert

1. Stop the gateway

openclaw gateway stop

2. If you made a backup earlier, restore it

Check for one:

ls -t ~/.openclaw/openclaw.json.backup.* | head -1

If it exists, restore it:

#
cp "$(ls -t ~/.openclaw/openclaw.json.backup.* | head -1)" ~/.openclaw/openclaw.json

3. If you want to fully abandon the templating setup, move those helper files aside

mv ~/.openclaw/openclaw.template.json5 ~/.openclaw/openclaw.template.json5.disabled 2>/dev/null || true
mv ~/.openclaw/.env ~/.openclaw/.env.disabled 2>/dev/null || true
mv ~/.openclaw/secrets.json ~/.openclaw/secrets.json.disabled 2>/dev/null || true

4. Put a simple working Discord config directly in ~/.openclaw/openclaw.json

Use literal values, no templating:

{
#
  channels: {
    discord: {
      enabled: true,
      token: "YOUR_DISCORD_BOT_TOKEN",

      groupPolicy: "allowlist",
      guilds: {
        "YOUR_SERVER_ID": {
          requireMention: false,
          users: ["YOUR_USER_ID"],
        },
      },

      dmPolicy: "allowlist",
      allowFrom: ["YOUR_USER_ID"],
#

      dm: {
        enabled: true,
      },
    },
  },
}

5. Start it again

openclaw gateway start
openclaw gateway status
openclaw status