add-discord — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited add-discord (Agent Skill) and scored it 92/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 2 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 2 flagged
The text {match} tells the agent to skip the normal "ask the user first" gate. Used adversarially it removes the human-in-the-loop check before destructive or sensitive actions, turning a normally-gated agent into a fire-and-forget executor.
The text {match} tells the agent to skip the normal "ask the user first" gate. Used adversarially it removes the human-in-the-loop check before destructive or sensitive actions, turning a normally-gated agent into a fire-and-forget executor.
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
Adds Discord bot support via the Chat SDK bridge. NanoClaw doesn't ship channels in trunk — this skill copies the Discord adapter in from the channels branch.
The mechanical steps under Apply carry nc: directive fences: an agent reads the prose and applies them, and a parser can apply them deterministically from the same document. Every directive is idempotent, so the whole skill is safe to re-run; anything a parser can't apply falls back to the prose beside it.
Fetch the channels branch and copy the Discord adapter and its registration test into src/channels/ (overwrite — the branch is canonical):
src/channels/discord.ts
src/channels/discord-registration.test.tsAppend the self-registration import to the channel barrel (skipped if the line is already present). This one line is the skill's only reach-in into core:
import './discord.js';Pinned to an exact version — the supply-chain policy rejects ranges and latest:
@chat-adapter/[email protected]Build first: it guards the typed createChatSdkBridge(...) core call and proves the dependency is installed. Then run the one integration test.
pnpm run buildpnpm exec vitest run src/channels/discord-registration.test.tsdiscord-registration.test.ts imports the real channel barrel and asserts the registry contains discord. It goes red if the import line is deleted or drifts, if the barrel fails to evaluate, or if @chat-adapter/discord isn't installed (the import throws) — so it also covers the dependency from step 3. End-to-end delivery against a real server is verified manually once the service runs.
Discord app setup is human and interactive — no parser can click through the Discord Developer Portal. The adapter is installed and registered, but it can't receive a message until the bot exists, has Message Content Intent, and shares a server with you. Tell the user:
Create the Discord bot:
1. Go to https://discord.com/developers/applications → New Application. Name it (e.g. "NanoClaw Assistant").
2. Bot tab → Add Bot if needed → Reset Token, then copy the Bot Token (it's shown only once).
3. Bot tab → Privileged Gateway Intents → enable Message Content Intent.
4. OAuth2 → URL Generator → Scopes: bot; Bot Permissions: Send Messages, Read Message History, Add Reactions, Attach Files, Use Slash Commands.
5. Open the generated URL and invite the bot to a server you're also in (a personal server is fine) — the bot can only DM you once you share a server.Paste the Bot Token (it's shown only once). You don't paste the Application ID or the Public Key by hand — the bot's own application record carries both, so a single call derives them from the token:
Paste the Bot Token — Bot tab. Click `Reset Token` if you need a new one.Read the application's own record. GET /oauth2/applications/@me returns the Application ID (id), the Public Key (verify_key), and your own account as the app's owner (owner.id) — so the App ID, the Public Key, and your Discord user ID all come from this one call instead of being copied by hand. A bad token fails here, before the restart, rather than silently later:
curl -sf https://discord.com/api/v10/oauth2/applications/@me -H "Authorization: Bot {{bot_token}}"Store the token and the two derived credentials — the adapter reads them from .env and fails to start without DISCORD_PUBLIC_KEY and DISCORD_APPLICATION_ID (set-if-absent, so a value you've already filled in is never overwritten):
DISCORD_BOT_TOKEN={{bot_token}}
DISCORD_APPLICATION_ID={{application_id}}
DISCORD_PUBLIC_KEY={{public_key}}Restart the service so it loads the Discord adapter and the credentials you just stored, and wait for its CLI socket before resolving:
bash setup/lib/restart.shThe bot can only DM you once it shares a server with you. If you didn't already invite it via the OAuth2 URL Generator while setting up the app, do it now: add the bot to a server you're also in (a personal server is fine). Tell the user:
Open the invite link — https://discord.com/oauth2/authorize?client_id={{application_id}}&scope=bot&permissions=2147584064 — and add the bot to a server you're also in (a personal server works fine); the bot can only DM you once you share a server. If you already invited it while setting up the app, you can skip this.The agent talks to you in your direct-message channel with the bot. Your Discord user ID was already derived as the application's owner (owner_handle), so all that's left is to open the DM and read back its channel id.
Open the DM with POST /users/@me/channels and take the channel id it returns as the conversation address discord:@me:<channelId> (if Discord refuses, the bot doesn't share a server with you yet — invite it, then retry):
curl -s -X POST https://discord.com/api/v10/users/@me/channels -H "Authorization: Bot {{bot_token}}" -H "Content-Type: application/json" -d '{"recipient_id":"{{owner_handle}}"}' | jq -er '"discord:@me:" + .id'owner_handle and platform_id are what the owner-wiring step needs. The greeting goes out over the DM channel, which works as soon as the bot shares a server with you.
If you're in the middle of /setup, return to the setup flow now. Otherwise wire this channel with /init-first-agent (or /manage-channels).
discorddiscord:@me:{dmChannelId} for the owner DM (e.g. discord:@me:1399...), discord:{guildId}:{channelId} for server channels — both IDs required for channels.discord:{guildId}:{channelId} — both IDs are required.The Bot Token paste is rejected. The token must be at least 50 characters of letters, digits, dots, underscores, and hyphens — a real Bot Token has two . separators. It lives under Bot → Reset Token in the Developer Portal and is shown only once; reset to get a fresh one. The short numeric Application ID and the OAuth2 Client Secret are different values and won't pass.
`applications/@me` returns 401. The token was reset since you copied it, or a stray space/newline came along with the paste. Reset the token in the Bot tab and re-run the check — it fails here on purpose, before the restart, while the credential is still cheap to fix.
The bot is online but never sees your messages. Two usual causes: Message Content Intent is off (Bot tab → Privileged Gateway Intents), so message bodies arrive empty and nothing triggers; or the bot doesn't share a server with you — in which case POST /users/@me/channels also refuses. Open the invite URL and add the bot to a server you're in, then retry.
Adapter looks installed but Discord never connects. Run pnpm exec vitest run src/channels/discord-registration.test.ts — red means the barrel import or the @chat-adapter/discord install drifted, so re-run the Apply steps. If it's green, the service probably hasn't restarted since the credentials were stored: bash setup/lib/restart.sh, then check logs/nanoclaw.error.log for missing DISCORD_PUBLIC_KEY / DISCORD_APPLICATION_ID complaints.
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.