From 5c231fc21f7d458edf2d766da336c817fb9796de Mon Sep 17 00:00:00 2001 From: Shadow Date: Sun, 25 Jan 2026 20:01:38 -0600 Subject: [PATCH 01/13] Doctor: warn on gateway exposure (#2016) Co-authored-by: Alex Alaniz --- CHANGELOG.md | 2 +- src/commands/doctor-security.ts | 55 +++++++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index afdbb8463..cacc265a3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,7 @@ Docs: https://docs.clawd.bot Status: unreleased. ### Changes -- TBD. +- Doctor: warn on gateway exposure without auth. (#2016) Thanks @Alex-Alaniz. ## 2026.1.24-3 diff --git a/src/commands/doctor-security.ts b/src/commands/doctor-security.ts index b3d82247f..483917faa 100644 --- a/src/commands/doctor-security.ts +++ b/src/commands/doctor-security.ts @@ -10,6 +10,61 @@ export async function noteSecurityWarnings(cfg: ClawdbotConfig) { const warnings: string[] = []; const auditHint = `- Run: ${formatCliCommand("clawdbot security audit --deep")}`; + // =========================================== + // GATEWAY NETWORK EXPOSURE CHECK + // =========================================== + // Check for dangerous gateway binding configurations + // that expose the gateway to network without proper auth + + const gatewayBind = cfg.gateway?.bind ?? "loopback"; + const customBindHost = cfg.gateway?.customBindHost?.trim(); + const authMode = cfg.gateway?.auth?.mode ?? "off"; + const authToken = cfg.gateway?.auth?.token; + const authPassword = cfg.gateway?.auth?.password; + + const isLoopbackBindHost = (host: string) => { + const normalized = host.trim().toLowerCase(); + return ( + normalized === "localhost" || + normalized === "::1" || + normalized === "[::1]" || + normalized.startsWith("127.") + ); + }; + + // Bindings that expose gateway beyond localhost + const exposedBindings = ["all", "lan", "0.0.0.0"]; + const isExposed = + exposedBindings.includes(gatewayBind) || + (gatewayBind === "custom" && (!customBindHost || !isLoopbackBindHost(customBindHost))); + + if (isExposed) { + if (authMode === "off") { + warnings.push( + `- CRITICAL: Gateway bound to "${gatewayBind}" with NO authentication.`, + ` Anyone on your network (or internet if port-forwarded) can fully control your agent.`, + ` Fix: ${formatCliCommand("clawdbot config set gateway.bind loopback")}`, + ` Or enable auth: ${formatCliCommand("clawdbot config set gateway.auth.mode token")}`, + ); + } else if (authMode === "token" && !authToken) { + warnings.push( + `- CRITICAL: Gateway bound to "${gatewayBind}" with empty auth token.`, + ` Fix: ${formatCliCommand("clawdbot doctor --fix")} to generate a token`, + ); + } else if (authMode === "password" && !authPassword) { + warnings.push( + `- CRITICAL: Gateway bound to "${gatewayBind}" with empty password.`, + ` Fix: ${formatCliCommand("clawdbot configure")} to set a password`, + ); + } else { + // Auth is configured, but still warn about network exposure + warnings.push( + `- WARNING: Gateway bound to "${gatewayBind}" (network-accessible).`, + ` Ensure your auth credentials are strong and not exposed.`, + ); + } + } + const warnDmPolicy = async (params: { label: string; provider: ChannelId; From 44bf454508322964c66f7c35f72fb935d8608617 Mon Sep 17 00:00:00 2001 From: Shadow Date: Sun, 25 Jan 2026 20:02:28 -0600 Subject: [PATCH 02/13] Docs: update clawtributors --- README.md | 55 ++++++++++++++++++++++++++++--------------------------- 1 file changed, 28 insertions(+), 27 deletions(-) diff --git a/README.md b/README.md index ebbdc43d5..47f3a9090 100644 --- a/README.md +++ b/README.md @@ -479,31 +479,32 @@ Thanks to all clawtributors:

steipete plum-dawg bohdanpodvirnyi iHildy joaohlisboa mneves75 MatthieuBizien MaudeBot Glucksberg rahthakor vrknetha radek-paclt Tobias Bischoff joshp123 czekaj mukhtharcm sebslight maxsumrall xadenryan rodrigouroz - juanpablodlc hsrvc magimetal meaningfool tyler6204 patelhiren NicholasSpisak jonisjongithub abhisekbasu1 zerone0x - jamesgroat claude JustYannicc SocialNerd42069 Hyaxia dantelex daveonkels google-labs-jules[bot] lc0rp mousberg - vignesh07 mteam88 Eng. Juan Combetto Mariano Belinky dbhurley TSavo julianengel bradleypriest benithors rohannagpal - timolins benostein f-trycua nachx639 pvoo sreekaransrinath gupsammy cristip73 stefangalescu nachoiacovino - Vasanth Rao Naik Sabavat petter-b cpojer scald gumadeiras andranik-sahakyan davidguttman sleontenko denysvitali sircrumpet - peschee rafaelreis-r thewilloftheshadow ratulsarna lutr0 danielz1z emanuelst KristijanJovanovski CashWilliams rdev - osolmaz joshrad-dev kiranjd adityashaw2 sheeek artuskg Takhoffman onutc pauloportella neooriginal - manuelhettich minghinmatthewlam myfunc travisirby buddyh connorshea kyleok mcinteerj dependabot[bot] John-Rood - timkrase uos-status gerardward2007 obviyus roshanasingh4 tosh-hamburg azade-c bjesuiter cheeeee Josh Phillips - robbyczgw-cla dlauer pookNast Whoaa512 YuriNachos chriseidhof ngutman ysqander aj47 superman32432432 - Yurii Chukhlib grp06 antons austinm911 blacksmith-sh[bot] damoahdominic dan-dr HeimdallStrategy imfing jalehman - jarvis-medmatic kkarimi mahmoudashraf93 pkrmf RandyVentures Ryan Lisse dougvk erikpr1994 Ghost jonasjancarik - Keith the Silly Goose L36 Server Marc mitschabaude-bot mkbehr neist sibbl chrisrodz Friederike Seiler gabriel-trigo - iamadig Jonathan D. Rhyne (DJ-D) Kit koala73 manmal ogulcancelik pasogott petradonka rubyrunsstuff siddhantjain - suminhthanh svkozak VACInc wes-davis zats 24601 adam91holt ameno- Chris Taylor Django Navarro - evalexpr henrino3 humanwritten larlyssa odysseus0 oswalpalash pcty-nextgen-service-account Syhids Aaron Konyer aaronveklabs - andreabadesso Andrii cash-echo-bot Clawd ClawdFx dguido EnzeD erik-agens Evizero fcatuhe - itsjaydesu ivancasco ivanrvpereira jayhickey jeffersonwarrior jeffersonwarrior jverdi longmaba mickahouan mjrussell - odnxe p6l-richard philipp-spiess robaxelsen Sash Catanzarite T5-AndyML travisp VAC william arzt zknicker - abhaymundhara alejandro maza andrewting19 anpoirier arthyn Asleep123 bolismauro conhecendoia dasilva333 Developer - Dimitrios Ploutarchos Drake Thomsen fal3 Felix Krause ganghyun kim grrowl gtsifrikas HazAT hrdwdmrbl hugobarauna - Jamie Openshaw Jarvis Jefferson Nunn Kevin Lin levifig Lloyd loukotal louzhixian martinpucik Matt mini - Miles mrdbstn MSch Mustafa Tag Eldeen ndraiman nexty5870 prathamdby ptn1411 reeltimeapps RLTCmpe - Rolf Fredheim Rony Kelner Samrat Jha senoldogann Seredeep sergical shiv19 siraht snopoke testingabc321 - The Admiral thesash Ubuntu voidserf Vultr-Clawd Admin Wimmie wstock yazinsai ymat19 Zach Knickerbocker - aaronn Alphonse-arianee Azade carlulsoe ddyo Erik latitudeki5223 Manuel Maly Mourad Boustani odrobnik - pcty-nextgen-ios-builder Quentin Randy Torres rhjoh ronak-guliani William Stock + juanpablodlc hsrvc magimetal meaningfool tyler6204 patelhiren NicholasSpisak jonisjongithub zerone0x abhisekbasu1 + jamesgroat claude JustYannicc Hyaxia dantelex SocialNerd42069 daveonkels google-labs-jules[bot] lc0rp mousberg + vignesh07 mteam88 dbhurley Mariano Belinky Eng. Juan Combetto TSavo julianengel bradleypriest benithors rohannagpal + timolins f-trycua benostein nachx639 pvoo sreekaransrinath gupsammy cristip73 stefangalescu nachoiacovino + Vasanth Rao Naik Sabavat petter-b cpojer scald gumadeiras andranik-sahakyan davidguttman sleontenko denysvitali orlyjamie + sircrumpet peschee rafaelreis-r thewilloftheshadow ratulsarna lutr0 danielz1z emanuelst KristijanJovanovski rdev + joshrad-dev kiranjd osolmaz adityashaw2 CashWilliams sheeek artuskg Takhoffman onutc pauloportella + neooriginal manuelhettich minghinmatthewlam myfunc travisirby buddyh connorshea kyleok mcinteerj dependabot[bot] + John-Rood timkrase uos-status gerardward2007 obviyus roshanasingh4 tosh-hamburg azade-c JonUleis bjesuiter + cheeeee Josh Phillips robbyczgw-cla dlauer pookNast Whoaa512 YuriNachos chriseidhof ngutman ysqander + aj47 superman32432432 Yurii Chukhlib grp06 antons austinm911 blacksmith-sh[bot] damoahdominic dan-dr HeimdallStrategy + imfing jalehman jarvis-medmatic kkarimi mahmoudashraf93 pkrmf RandyVentures Ryan Lisse dougvk erikpr1994 + Ghost jonasjancarik Keith the Silly Goose L36 Server Marc mitschabaude-bot mkbehr neist sibbl chrisrodz + Friederike Seiler gabriel-trigo iamadig Jonathan D. Rhyne (DJ-D) Kit koala73 manmal ogulcancelik pasogott petradonka + rubyrunsstuff siddhantjain suminhthanh svkozak VACInc wes-davis zats 24601 adam91holt ameno- + Chris Taylor Django Navarro evalexpr henrino3 humanwritten larlyssa odysseus0 oswalpalash pcty-nextgen-service-account rmorse + Syhids Aaron Konyer aaronveklabs andreabadesso Andrii cash-echo-bot Clawd ClawdFx dguido EnzeD + erik-agens Evizero fcatuhe itsjaydesu ivancasco ivanrvpereira jayhickey jeffersonwarrior jeffersonwarrior jverdi + longmaba mickahouan mjrussell odnxe p6l-richard philipp-spiess robaxelsen Sash Catanzarite T5-AndyML travisp + VAC william arzt zknicker abhaymundhara alejandro maza andrewting19 anpoirier arthyn Asleep123 bolismauro + conhecendoia dasilva333 Developer Dimitrios Ploutarchos Drake Thomsen fal3 Felix Krause foeken ganghyun kim grrowl + gtsifrikas HazAT hrdwdmrbl hugobarauna Jamie Openshaw Jarvis Jefferson Nunn Kevin Lin kitze levifig + Lloyd loukotal louzhixian martinpucik Matt mini Miles mrdbstn MSch Mustafa Tag Eldeen ndraiman + nexty5870 Noctivoro prathamdby ptn1411 reeltimeapps RLTCmpe Rolf Fredheim Rony Kelner Samrat Jha senoldogann + Seredeep sergical shiv19 shiyuanhai siraht snopoke testingabc321 The Admiral thesash Ubuntu + voidserf Vultr-Clawd Admin Wimmie wstock yazinsai ymat19 Zach Knickerbocker aaronn Alphonse-arianee Azade + carlulsoe ddyo Erik latitudeki5223 Manuel Maly Mourad Boustani odrobnik pcty-nextgen-ios-builder Quentin Randy Torres + rhjoh ronak-guliani William Stock

From 7ea4b06a046ad1bdf979941c605e0fbea81a664d Mon Sep 17 00:00:00 2001 From: Shadow Date: Sun, 25 Jan 2026 20:05:00 -0600 Subject: [PATCH 03/13] Deps: revert native-preview to published version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 2a841139f..0c63d5d69 100644 --- a/package.json +++ b/package.json @@ -220,7 +220,7 @@ "@types/proper-lockfile": "^4.1.4", "@types/qrcode-terminal": "^0.12.2", "@types/ws": "^8.18.1", - "@typescript/native-preview": "7.0.0-dev.20260125.1", + "@typescript/native-preview": "7.0.0-dev.20260124.1", "@vitest/coverage-v8": "^4.0.18", "docx-preview": "^0.3.7", "lit": "^3.3.2", From 138916a0d1a20e613dd2db98239877244c5ad1e9 Mon Sep 17 00:00:00 2001 From: Shadow Date: Sun, 25 Jan 2026 20:11:21 -0600 Subject: [PATCH 04/13] Deps: sync memory-core lockfile spec --- pnpm-lock.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 781a461a9..14bef9f5c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -357,7 +357,7 @@ importers: extensions/memory-core: dependencies: clawdbot: - specifier: '>=2026.1.24' + specifier: '>=2026.1.25' version: link:../.. extensions/memory-lancedb: From 9c26cded75615cdd2683981a21633b4b6fb799fa Mon Sep 17 00:00:00 2001 From: Shadow Date: Sun, 25 Jan 2026 20:22:10 -0600 Subject: [PATCH 05/13] Docs: add Vercel AI Gateway sidebar entry (#1901) Co-authored-by: Jerilyn Zheng --- CHANGELOG.md | 1 + docs/docs.json | 1 + docs/providers/vercel-ai-gateway.md | 1 + 3 files changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index cacc265a3..5e4a7005d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ Status: unreleased. ### Changes - Doctor: warn on gateway exposure without auth. (#2016) Thanks @Alex-Alaniz. +- Docs: add Vercel AI Gateway to providers sidebar. (#1901) Thanks @jerilynzheng. ## 2026.1.24-3 diff --git a/docs/docs.json b/docs/docs.json index 09b248990..4af7943e0 100644 --- a/docs/docs.json +++ b/docs/docs.json @@ -983,6 +983,7 @@ "bedrock", "providers/moonshot", "providers/minimax", + "providers/vercel-ai-gateway", "providers/openrouter", "providers/synthetic", "providers/opencode", diff --git a/docs/providers/vercel-ai-gateway.md b/docs/providers/vercel-ai-gateway.md index bd31f0a87..36cf51cda 100644 --- a/docs/providers/vercel-ai-gateway.md +++ b/docs/providers/vercel-ai-gateway.md @@ -1,4 +1,5 @@ --- +title: "Vercel AI Gateway" summary: "Vercel AI Gateway setup (auth + model selection)" read_when: - You want to use Vercel AI Gateway with Clawdbot From c7fabb43f98e27c95fffc656dded87e7a9371355 Mon Sep 17 00:00:00 2001 From: Shadow Date: Sun, 25 Jan 2026 20:23:40 -0600 Subject: [PATCH 06/13] Agents: expand cron tool description (#1988) Co-authored-by: Tomas Cupr --- CHANGELOG.md | 1 + src/agents/tools/cron-tool.ts | 46 +++++++++++++++++++++++++++++++++-- 2 files changed, 45 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5e4a7005d..44a2e6021 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ Status: unreleased. ### Changes - Doctor: warn on gateway exposure without auth. (#2016) Thanks @Alex-Alaniz. - Docs: add Vercel AI Gateway to providers sidebar. (#1901) Thanks @jerilynzheng. +- Agents: expand cron tool description with full schema docs. (#1988) Thanks @tomascupr. ## 2026.1.24-3 diff --git a/src/agents/tools/cron-tool.ts b/src/agents/tools/cron-tool.ts index a1d218dd7..739b3ada3 100644 --- a/src/agents/tools/cron-tool.ts +++ b/src/agents/tools/cron-tool.ts @@ -133,8 +133,50 @@ export function createCronTool(opts?: CronToolOptions): AnyAgentTool { return { label: "Cron", name: "cron", - description: - "Manage Gateway cron jobs (status/list/add/update/remove/run/runs) and send wake events. Use `jobId` as the canonical identifier; `id` is accepted for compatibility. Use `contextMessages` (0-10) to add previous messages as context to the job text.", + description: `Manage Gateway cron jobs (status/list/add/update/remove/run/runs) and send wake events. + +ACTIONS: +- status: Check cron scheduler status +- list: List jobs (use includeDisabled:true to include disabled) +- add: Create job (requires job object, see schema below) +- update: Modify job (requires jobId + patch object) +- remove: Delete job (requires jobId) +- run: Trigger job immediately (requires jobId) +- runs: Get job run history (requires jobId) +- wake: Send wake event (requires text, optional mode) + +JOB SCHEMA (for add action): +{ + "name": "string (optional)", + "schedule": { ... }, // Required: when to run + "payload": { ... }, // Required: what to execute + "sessionTarget": "main" | "isolated", // Required + "enabled": true | false // Optional, default true +} + +SCHEDULE TYPES (schedule.kind): +- "at": One-shot at absolute time + { "kind": "at", "atMs": } +- "every": Recurring interval + { "kind": "every", "everyMs": , "anchorMs": } +- "cron": Cron expression + { "kind": "cron", "expr": "", "tz": "" } + +PAYLOAD TYPES (payload.kind): +- "systemEvent": Injects text as system event into session + { "kind": "systemEvent", "text": "" } +- "agentTurn": Runs agent with message (isolated sessions only) + { "kind": "agentTurn", "message": "", "model": "", "thinking": "", "timeoutSeconds": , "deliver": , "channel": "", "to": "", "bestEffortDeliver": } + +CRITICAL CONSTRAINTS: +- sessionTarget="main" REQUIRES payload.kind="systemEvent" +- sessionTarget="isolated" REQUIRES payload.kind="agentTurn" + +WAKE MODES (for wake action): +- "next-heartbeat" (default): Wake on next heartbeat +- "now": Wake immediately + +Use jobId as the canonical identifier; id is accepted for compatibility. Use contextMessages (0-10) to add previous messages as context to the job text.`, parameters: CronToolSchema, execute: async (_toolCallId, args) => { const params = args as Record; From a21671ed5b3f034aa89940a53e375d19b199b1de Mon Sep 17 00:00:00 2001 From: Shadow Date: Sun, 25 Jan 2026 20:25:08 -0600 Subject: [PATCH 07/13] Skills: add missing dependency metadata (#1995) Co-authored-by: jackheuberger --- CHANGELOG.md | 1 + skills/discord/SKILL.md | 1 + skills/github/SKILL.md | 1 + skills/notion/SKILL.md | 2 +- skills/slack/SKILL.md | 1 + 5 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 44a2e6021..425b21b1e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ Status: unreleased. - Doctor: warn on gateway exposure without auth. (#2016) Thanks @Alex-Alaniz. - Docs: add Vercel AI Gateway to providers sidebar. (#1901) Thanks @jerilynzheng. - Agents: expand cron tool description with full schema docs. (#1988) Thanks @tomascupr. +- Skills: add missing dependency metadata for GitHub, Notion, Slack, Discord. (#1995) Thanks @jackheuberger. ## 2026.1.24-3 diff --git a/skills/discord/SKILL.md b/skills/discord/SKILL.md index 0b64f14e1..5525a3bf5 100644 --- a/skills/discord/SKILL.md +++ b/skills/discord/SKILL.md @@ -1,6 +1,7 @@ --- name: discord description: Use when you need to control Discord from Clawdbot via the discord tool: send messages, react, post or upload stickers, upload emojis, run polls, manage threads/pins/search, create/edit/delete channels and categories, fetch permissions or member/role/channel info, or handle moderation actions in Discord DMs or channels. +metadata: {"clawdbot":{"emoji":"๐ŸŽฎ","requires":{"config":["channels.discord"]}}} --- # Discord Actions diff --git a/skills/github/SKILL.md b/skills/github/SKILL.md index 03b2a0033..e7c89f7ba 100644 --- a/skills/github/SKILL.md +++ b/skills/github/SKILL.md @@ -1,6 +1,7 @@ --- name: github description: "Interact with GitHub using the `gh` CLI. Use `gh issue`, `gh pr`, `gh run`, and `gh api` for issues, PRs, CI runs, and advanced queries." +metadata: {"clawdbot":{"emoji":"๐Ÿ™","requires":{"bins":["gh"]},"install":[{"id":"brew","kind":"brew","formula":"gh","bins":["gh"],"label":"Install GitHub CLI (brew)"},{"id":"apt","kind":"apt","package":"gh","bins":["gh"],"label":"Install GitHub CLI (apt)"}]}} --- # GitHub Skill diff --git a/skills/notion/SKILL.md b/skills/notion/SKILL.md index 869871b3c..04921e250 100644 --- a/skills/notion/SKILL.md +++ b/skills/notion/SKILL.md @@ -2,7 +2,7 @@ name: notion description: Notion API for creating and managing pages, databases, and blocks. homepage: https://developers.notion.com -metadata: {"clawdbot":{"emoji":"๐Ÿ“"}} +metadata: {"clawdbot":{"emoji":"๐Ÿ“","requires":{"env":["NOTION_API_KEY"]},"primaryEnv":"NOTION_API_KEY"}} --- # notion diff --git a/skills/slack/SKILL.md b/skills/slack/SKILL.md index df04f858f..b72bab1f3 100644 --- a/skills/slack/SKILL.md +++ b/skills/slack/SKILL.md @@ -1,6 +1,7 @@ --- name: slack description: Use when you need to control Slack from Clawdbot via the slack tool, including reacting to messages or pinning/unpinning items in Slack channels or DMs. +metadata: {"clawdbot":{"emoji":"๐Ÿ’ฌ","requires":{"config":["channels.slack"]}}} --- # Slack Actions From 136f0d4d1d5028516f4824314a6db5ebd06871af Mon Sep 17 00:00:00 2001 From: Shadow Date: Sun, 25 Jan 2026 20:28:53 -0600 Subject: [PATCH 08/13] Docs: add Render deployment guide (#1975) Co-authored-by: Anurag Goel --- CHANGELOG.md | 1 + docs/docs.json | 1 + docs/render.mdx | 158 ++++++++++++++++++++++++++++++++++++++++++++++++ render.yaml | 21 +++++++ 4 files changed, 181 insertions(+) create mode 100644 docs/render.mdx create mode 100644 render.yaml diff --git a/CHANGELOG.md b/CHANGELOG.md index 425b21b1e..6abd9fc53 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ Status: unreleased. - Docs: add Vercel AI Gateway to providers sidebar. (#1901) Thanks @jerilynzheng. - Agents: expand cron tool description with full schema docs. (#1988) Thanks @tomascupr. - Skills: add missing dependency metadata for GitHub, Notion, Slack, Discord. (#1995) Thanks @jackheuberger. +- Docs: add Render deployment guide. (#1975) Thanks @anurag. ## 2026.1.24-3 diff --git a/docs/docs.json b/docs/docs.json index 4af7943e0..983585bff 100644 --- a/docs/docs.json +++ b/docs/docs.json @@ -827,6 +827,7 @@ "install/nix", "install/docker", "railway", + "render", "install/bun" ] }, diff --git a/docs/render.mdx b/docs/render.mdx new file mode 100644 index 000000000..3fcdae07a --- /dev/null +++ b/docs/render.mdx @@ -0,0 +1,158 @@ +--- +title: Deploy on Render +--- + +Deploy Clawdbot on Render using Infrastructure as Code. The included `render.yaml` Blueprint defines your entire stack declaratively, service, disk, environment variables, so you can deploy with a single click and version your infrastructure alongside your code. + +## Prerequisites + +- A [Render account](https://render.com) (free tier available) +- An API key from your preferred [model provider](/providers) + +## Deploy with a Render Blueprint + +Deploy to Render + +Clicking this link will: + +1. Create a new Render service from the `render.yaml` Blueprint at the root of this repo. +2. Prompt you to set `SETUP_PASSWORD` +3. Build the Docker image and deploy + +Once deployed, your service URL follows the pattern `https://.onrender.com`. + +## Understanding the Blueprint + +Render Blueprints are YAML files that define your infrastructure. The `render.yaml` in this +repository configures everything needed to run Clawdbot: + +```yaml +services: + - type: web + name: clawdbot + runtime: docker + plan: starter + healthCheckPath: /health + envVars: + - key: PORT + value: "8080" + - key: SETUP_PASSWORD + sync: false # prompts during deploy + - key: CLAWDBOT_STATE_DIR + value: /data/.clawdbot + - key: CLAWDBOT_WORKSPACE_DIR + value: /data/workspace + - key: CLAWDBOT_GATEWAY_TOKEN + generateValue: true # auto-generates a secure token + disk: + name: clawdbot-data + mountPath: /data + sizeGB: 1 +``` + +Key Blueprint features used: + +| Feature | Purpose | +|---------|---------| +| `runtime: docker` | Builds from the repo's Dockerfile | +| `healthCheckPath` | Render monitors `/health` and restarts unhealthy instances | +| `sync: false` | Prompts for value during deploy (secrets) | +| `generateValue: true` | Auto-generates a cryptographically secure value | +| `disk` | Persistent storage that survives redeploys | + +## Choosing a plan + +| Plan | Spin-down | Disk | Best for | +|------|-----------|------|----------| +| Free | After 15 min idle | Not available | Testing, demos | +| Starter | Never | 1GB+ | Personal use, small teams | +| Standard+ | Never | 1GB+ | Production, multiple channels | + +The Blueprint defaults to `starter`. To use free tier, change `plan: free` in your fork's +`render.yaml` (but note: no persistent disk means config resets on each deploy). + +## After deployment + +### Complete the setup wizard + +1. Navigate to `https://.onrender.com/setup` +2. Enter your `SETUP_PASSWORD` +3. Select a model provider and paste your API key +4. Optionally configure messaging channels (Telegram, Discord, Slack) +5. Click **Run setup** + +### Access the Control UI + +The web dashboard is available at `https://.onrender.com/clawdbot`. + +## Render Dashboard features + +### Logs + +View real-time logs in **Dashboard โ†’ your service โ†’ Logs**. Filter by: +- Build logs (Docker image creation) +- Deploy logs (service startup) +- Runtime logs (application output) + +### Shell access + +For debugging, open a shell session via **Dashboard โ†’ your service โ†’ Shell**. The persistent disk is mounted at `/data`. + +### Environment variables + +Modify variables in **Dashboard โ†’ your service โ†’ Environment**. Changes trigger an automatic redeploy. + +### Auto-deploy + +If you use the original Clawdbot repository, Render will not auto-deploy your Clawdbot. To update it, run a manual Blueprint sync from the dashboard. + +## Custom domain + +1. Go to **Dashboard โ†’ your service โ†’ Settings โ†’ Custom Domains** +2. Add your domain +3. Configure DNS as instructed (CNAME to `*.onrender.com`) +4. Render provisions a TLS certificate automatically + +## Scaling + +Render supports horizontal and vertical scaling: + +- **Vertical**: Change the plan to get more CPU/RAM +- **Horizontal**: Increase instance count (Standard plan and above) + +For Clawdbot, vertical scaling is usually sufficient. Horizontal scaling requires sticky sessions or external state management. + +## Backups and migration + +Export your configuration and workspace at any time: + +``` +https://.onrender.com/setup/export +``` + +This downloads a portable backup you can restore on any Clawdbot host. + +## Troubleshooting + +### Service won't start + +Check the deploy logs in the Render Dashboard. Common issues: + +- Missing `SETUP_PASSWORD` โ€” the Blueprint prompts for this, but verify it's set +- Port mismatch โ€” ensure `PORT=8080` matches the Dockerfile's exposed port + +### Slow cold starts (free tier) + +Free tier services spin down after 15 minutes of inactivity. The first request after spin-down takes a few seconds while the container starts. Upgrade to Starter plan for always-on. + +### Data loss after redeploy + +This happens on free tier (no persistent disk). Upgrade to a paid plan, or +regularly export your config via `/setup/export`. + +### Health check failures + +Render expects a 200 response from `/health` within 30 seconds. If builds succeed but deploys fail, the service may be taking too long to start. Check: + +- Build logs for errors +- Whether the container runs locally with `docker build && docker run` diff --git a/render.yaml b/render.yaml new file mode 100644 index 000000000..01923a8f6 --- /dev/null +++ b/render.yaml @@ -0,0 +1,21 @@ +services: + - type: web + name: clawdbot + runtime: docker + plan: starter + healthCheckPath: /health + envVars: + - key: PORT + value: "8080" + - key: SETUP_PASSWORD + sync: false + - key: CLAWDBOT_STATE_DIR + value: /data/.clawdbot + - key: CLAWDBOT_WORKSPACE_DIR + value: /data/workspace + - key: CLAWDBOT_GATEWAY_TOKEN + generateValue: true + disk: + name: clawdbot-data + mountPath: /data + sizeGB: 1 From 6b6284c69cda6193bc0de5d178ed0e8e0ea251e2 Mon Sep 17 00:00:00 2001 From: Shadow Date: Sun, 25 Jan 2026 20:37:20 -0600 Subject: [PATCH 09/13] CI: add PR labeler + label sync --- .github/labeler.yml | 150 ++++++++++++++++++++++++++++ .github/workflows/auto-response.yml | 59 +++++++++++ .github/workflows/labeler.yml | 17 ++++ scripts/sync-labels.ts | 91 +++++++++++++++++ 4 files changed, 317 insertions(+) create mode 100644 .github/labeler.yml create mode 100644 .github/workflows/auto-response.yml create mode 100644 .github/workflows/labeler.yml create mode 100644 scripts/sync-labels.ts diff --git a/.github/labeler.yml b/.github/labeler.yml new file mode 100644 index 000000000..0f3344acc --- /dev/null +++ b/.github/labeler.yml @@ -0,0 +1,150 @@ +"channel: bluebubbles": + - "extensions/bluebubbles/**" + - "docs/channels/bluebubbles.md" +"channel: discord": + - "src/discord/**" + - "extensions/discord/**" + - "docs/channels/discord.md" +"channel: googlechat": + - "extensions/googlechat/**" + - "docs/channels/googlechat.md" +"channel: imessage": + - "src/imessage/**" + - "extensions/imessage/**" + - "docs/channels/imessage.md" +"channel: line": + - "extensions/line/**" +"channel: matrix": + - "extensions/matrix/**" + - "docs/channels/matrix.md" +"channel: mattermost": + - "extensions/mattermost/**" + - "docs/channels/mattermost.md" +"channel: msteams": + - "extensions/msteams/**" + - "docs/channels/msteams.md" +"channel: nextcloud-talk": + - "extensions/nextcloud-talk/**" + - "docs/channels/nextcloud-talk.md" +"channel: nostr": + - "extensions/nostr/**" + - "docs/channels/nostr.md" +"channel: signal": + - "src/signal/**" + - "extensions/signal/**" + - "docs/channels/signal.md" +"channel: slack": + - "src/slack/**" + - "extensions/slack/**" + - "docs/channels/slack.md" +"channel: telegram": + - "src/telegram/**" + - "extensions/telegram/**" + - "docs/channels/telegram.md" +"channel: tlon": + - "extensions/tlon/**" + - "docs/channels/tlon.md" +"channel: voice-call": + - "extensions/voice-call/**" +"channel: whatsapp-web": + - "src/web/**" + - "extensions/whatsapp/**" + - "docs/channels/whatsapp.md" +"channel: zalo": + - "extensions/zalo/**" + - "docs/channels/zalo.md" +"channel: zalouser": + - "extensions/zalouser/**" + - "docs/channels/zalouser.md" + +"app: android": + - "apps/android/**" + - "docs/platforms/android.md" +"app: ios": + - "apps/ios/**" + - "docs/platforms/ios.md" +"app: macos": + - "apps/macos/**" + - "docs/platforms/macos.md" + - "docs/platforms/mac/**" +"app: web-ui": + - "ui/**" + - "src/gateway/control-ui.ts" + - "src/gateway/control-ui-shared.ts" + - "src/infra/control-ui-assets.ts" + +"cli": + - "src/cli/**" + - "src/commands/**" + - "src/tui/**" + +"gateway": + - "src/gateway/**" + - "src/daemon/**" + - "docs/gateway/**" + +"docs": + - "docs/**" + - "docs.acp.md" + - "README.md" + - "README-header.png" + - "CHANGELOG.md" + - "CONTRIBUTING.md" + - "SECURITY.md" + +"extensions: bluebubbles": + - "extensions/bluebubbles/**" +"extensions: copilot-proxy": + - "extensions/copilot-proxy/**" +"extensions: diagnostics-otel": + - "extensions/diagnostics-otel/**" +"extensions: discord": + - "extensions/discord/**" +"extensions: google-antigravity-auth": + - "extensions/google-antigravity-auth/**" +"extensions: google-gemini-cli-auth": + - "extensions/google-gemini-cli-auth/**" +"extensions: googlechat": + - "extensions/googlechat/**" +"extensions: imessage": + - "extensions/imessage/**" +"extensions: line": + - "extensions/line/**" +"extensions: llm-task": + - "extensions/llm-task/**" +"extensions: lobster": + - "extensions/lobster/**" +"extensions: matrix": + - "extensions/matrix/**" +"extensions: mattermost": + - "extensions/mattermost/**" +"extensions: memory-core": + - "extensions/memory-core/**" +"extensions: memory-lancedb": + - "extensions/memory-lancedb/**" +"extensions: msteams": + - "extensions/msteams/**" +"extensions: nextcloud-talk": + - "extensions/nextcloud-talk/**" +"extensions: nostr": + - "extensions/nostr/**" +"extensions: open-prose": + - "extensions/open-prose/**" +"extensions: qwen-portal-auth": + - "extensions/qwen-portal-auth/**" +"extensions: signal": + - "extensions/signal/**" +"extensions: slack": + - "extensions/slack/**" +"extensions: telegram": + - "extensions/telegram/**" +"extensions: tlon": + - "extensions/tlon/**" +"extensions: voice-call": + - "extensions/voice-call/**" +"extensions: whatsapp": + - "extensions/whatsapp/**" +"extensions: zalo": + - "extensions/zalo/**" +"extensions: zalouser": + - "extensions/zalouser/**" diff --git a/.github/workflows/auto-response.yml b/.github/workflows/auto-response.yml new file mode 100644 index 000000000..7f242a094 --- /dev/null +++ b/.github/workflows/auto-response.yml @@ -0,0 +1,59 @@ +name: Auto response + +on: + issues: + types: [labeled] + pull_request: + types: [labeled] + +permissions: + issues: write + pull-requests: write + +jobs: + auto-response: + runs-on: ubuntu-latest + steps: + - name: Handle labeled items + uses: actions/github-script@v7 + with: + script: | + const rules = [ + { + label: "skill-clawdhub", + close: true, + message: + "Thanks for the contribution! New skills should be published to Clawdhub for everyone to use. Weโ€™re keeping the core lean on skills, so Iโ€™m closing this out.", + }, + ]; + + const labelName = context.payload.label?.name; + if (!labelName) { + return; + } + + const rule = rules.find((item) => item.label === labelName); + if (!rule) { + return; + } + + const issueNumber = context.payload.issue?.number ?? context.payload.pull_request?.number; + if (!issueNumber) { + return; + } + + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: issueNumber, + body: rule.message, + }); + + if (rule.close) { + await github.rest.issues.update({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: issueNumber, + state: "closed", + }); + } diff --git a/.github/workflows/labeler.yml b/.github/workflows/labeler.yml new file mode 100644 index 000000000..6ec73a1a3 --- /dev/null +++ b/.github/workflows/labeler.yml @@ -0,0 +1,17 @@ +name: Labeler + +on: + pull_request_target: + types: [opened, synchronize, reopened] + +permissions: + contents: read + pull-requests: write + +jobs: + label: + runs-on: ubuntu-latest + steps: + - uses: actions/labeler@v5 + with: + configuration-path: .github/labeler.yml diff --git a/scripts/sync-labels.ts b/scripts/sync-labels.ts new file mode 100644 index 000000000..0220e911a --- /dev/null +++ b/scripts/sync-labels.ts @@ -0,0 +1,91 @@ +import { execFileSync } from "node:child_process"; +import { readFileSync } from "node:fs"; +import { resolve } from "node:path"; +import yaml from "yaml"; + +type LabelConfig = Record; + +type RepoLabel = { + name: string; + color?: string; +}; + +const COLOR_BY_PREFIX = new Map([ + ["channel", "1d76db"], + ["app", "6f42c1"], + ["extensions", "0e8a16"], + ["docs", "0075ca"], + ["cli", "f9d0c4"], + ["gateway", "d4c5f9"], +]); + +const configPath = resolve(".github/labeler.yml"); +const config = yaml.parse(readFileSync(configPath, "utf8")) as LabelConfig; + +if (!config || typeof config !== "object") { + throw new Error("labeler.yml must be a mapping of label names to globs."); +} + +const labelNames = Object.keys(config).filter(Boolean); +const repo = resolveRepo(); +const existing = fetchExistingLabels(repo); + +const missing = labelNames.filter((label) => !existing.has(label)); +if (!missing.length) { + console.log("All labeler labels already exist."); + process.exit(0); +} + +for (const label of missing) { + const color = pickColor(label); + execFileSync( + "gh", + [ + "api", + "-X", + "POST", + `repos/${repo}/labels`, + "-f", + `name=${label}`, + "-f", + `color=${color}`, + ], + { stdio: "inherit" }, + ); + console.log(`Created label: ${label}`); +} + +function pickColor(label: string): string { + const prefix = label.includes(":") ? label.split(":", 1)[0].trim() : label.trim(); + return COLOR_BY_PREFIX.get(prefix) ?? "ededed"; +} + +function resolveRepo(): string { + const remote = execFileSync("git", ["config", "--get", "remote.origin.url"], { + encoding: "utf8", + }).trim(); + + if (!remote) { + throw new Error("Unable to determine repository from git remote."); + } + + if (remote.startsWith("git@github.com:")) { + return remote.replace("git@github.com:", "").replace(/\.git$/, ""); + } + + if (remote.startsWith("https://github.com/")) { + return remote.replace("https://github.com/", "").replace(/\.git$/, ""); + } + + throw new Error(`Unsupported GitHub remote: ${remote}`); +} + +function fetchExistingLabels(repo: string): Map { + const raw = execFileSync( + "gh", + ["api", `repos/${repo}/labels?per_page=100`, "--paginate"], + { encoding: "utf8" }, + ); + const labels = JSON.parse(raw) as RepoLabel[]; + return new Map(labels.map((label) => [label.name, label])); +} From b25fcaef0f14293886f020231e169be51bb3da45 Mon Sep 17 00:00:00 2001 From: Shadow Date: Sun, 25 Jan 2026 20:38:44 -0600 Subject: [PATCH 10/13] CI: parse labeler without deps --- scripts/sync-labels.ts | 30 +++++++++++++++++++++++------- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/scripts/sync-labels.ts b/scripts/sync-labels.ts index 0220e911a..297644c1e 100644 --- a/scripts/sync-labels.ts +++ b/scripts/sync-labels.ts @@ -1,9 +1,6 @@ import { execFileSync } from "node:child_process"; import { readFileSync } from "node:fs"; import { resolve } from "node:path"; -import yaml from "yaml"; - -type LabelConfig = Record; type RepoLabel = { name: string; @@ -20,13 +17,12 @@ const COLOR_BY_PREFIX = new Map([ ]); const configPath = resolve(".github/labeler.yml"); -const config = yaml.parse(readFileSync(configPath, "utf8")) as LabelConfig; +const labelNames = extractLabelNames(readFileSync(configPath, "utf8")); -if (!config || typeof config !== "object") { - throw new Error("labeler.yml must be a mapping of label names to globs."); +if (!labelNames.length) { + throw new Error("labeler.yml must declare at least one label."); } -const labelNames = Object.keys(config).filter(Boolean); const repo = resolveRepo(); const existing = fetchExistingLabels(repo); @@ -55,6 +51,26 @@ for (const label of missing) { console.log(`Created label: ${label}`); } +function extractLabelNames(contents: string): string[] { + const labels: string[] = []; + for (const line of contents.split("\n")) { + if (!line.trim() || line.trimStart().startsWith("#")) { + continue; + } + if (/^\s/.test(line)) { + continue; + } + const match = line.match(/^(["'])(.+)\1\s*:/) ?? line.match(/^([^:]+):/); + if (match) { + const name = (match[2] ?? match[1] ?? "").trim(); + if (name) { + labels.push(name); + } + } + } + return labels; +} + function pickColor(label: string): string { const prefix = label.includes(":") ? label.split(":", 1)[0].trim() : label.trim(); return COLOR_BY_PREFIX.get(prefix) ?? "ededed"; From 28fe95ac5ef56c50bb5c7a8c47307fb83060ba71 Mon Sep 17 00:00:00 2001 From: Shadow Date: Sun, 25 Jan 2026 20:39:44 -0600 Subject: [PATCH 11/13] Docs: note labeler updates --- .github/labeler.yml | 41 ----------------------------------------- AGENTS.md | 1 + 2 files changed, 1 insertion(+), 41 deletions(-) diff --git a/.github/labeler.yml b/.github/labeler.yml index 0f3344acc..0c3d863cf 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml @@ -86,65 +86,24 @@ "docs": - "docs/**" - "docs.acp.md" - - "README.md" - - "README-header.png" - - "CHANGELOG.md" - - "CONTRIBUTING.md" - - "SECURITY.md" -"extensions: bluebubbles": - - "extensions/bluebubbles/**" "extensions: copilot-proxy": - "extensions/copilot-proxy/**" "extensions: diagnostics-otel": - "extensions/diagnostics-otel/**" -"extensions: discord": - - "extensions/discord/**" "extensions: google-antigravity-auth": - "extensions/google-antigravity-auth/**" "extensions: google-gemini-cli-auth": - "extensions/google-gemini-cli-auth/**" -"extensions: googlechat": - - "extensions/googlechat/**" -"extensions: imessage": - - "extensions/imessage/**" -"extensions: line": - - "extensions/line/**" "extensions: llm-task": - "extensions/llm-task/**" "extensions: lobster": - "extensions/lobster/**" -"extensions: matrix": - - "extensions/matrix/**" -"extensions: mattermost": - - "extensions/mattermost/**" "extensions: memory-core": - "extensions/memory-core/**" "extensions: memory-lancedb": - "extensions/memory-lancedb/**" -"extensions: msteams": - - "extensions/msteams/**" -"extensions: nextcloud-talk": - - "extensions/nextcloud-talk/**" -"extensions: nostr": - - "extensions/nostr/**" "extensions: open-prose": - "extensions/open-prose/**" "extensions: qwen-portal-auth": - "extensions/qwen-portal-auth/**" -"extensions: signal": - - "extensions/signal/**" -"extensions: slack": - - "extensions/slack/**" -"extensions: telegram": - - "extensions/telegram/**" -"extensions: tlon": - - "extensions/tlon/**" -"extensions: voice-call": - - "extensions/voice-call/**" -"extensions: whatsapp": - - "extensions/whatsapp/**" -"extensions: zalo": - - "extensions/zalo/**" -"extensions: zalouser": - - "extensions/zalouser/**" diff --git a/AGENTS.md b/AGENTS.md index deed6d9bd..ac85a00d8 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -13,6 +13,7 @@ - Core channel docs: `docs/channels/` - Core channel code: `src/telegram`, `src/discord`, `src/slack`, `src/signal`, `src/imessage`, `src/web` (WhatsApp web), `src/channels`, `src/routing` - Extensions (channel plugins): `extensions/*` (e.g. `extensions/msteams`, `extensions/matrix`, `extensions/zalo`, `extensions/zalouser`, `extensions/voice-call`) +- When adding channels/extensions/apps/docs, review `.github/labeler.yml` for label coverage. ## Docs Linking (Mintlify) - Docs are hosted on Mintlify (docs.clawd.bot). From 9c8e8c5c2d531e58cfe7fe0714a4530fa10c8016 Mon Sep 17 00:00:00 2001 From: Shadow Date: Sun, 25 Jan 2026 20:45:42 -0600 Subject: [PATCH 12/13] CI: increase Node heap size for macOS checks (#1890) Co-authored-by: Zach Knickerbocker --- .github/workflows/ci.yml | 2 ++ CHANGELOG.md | 1 + 2 files changed, 3 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fcd8e457c..8cc86bd63 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -342,6 +342,8 @@ jobs: pnpm install --frozen-lockfile --ignore-scripts=false --config.engine-strict=false --config.enable-pre-post-scripts=true || pnpm install --frozen-lockfile --ignore-scripts=false --config.engine-strict=false --config.enable-pre-post-scripts=true - name: Run ${{ matrix.task }} + env: + NODE_OPTIONS: --max-old-space-size=4096 run: ${{ matrix.command }} macos-app: diff --git a/CHANGELOG.md b/CHANGELOG.md index 6abd9fc53..93b171b38 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ Status: unreleased. - Agents: expand cron tool description with full schema docs. (#1988) Thanks @tomascupr. - Skills: add missing dependency metadata for GitHub, Notion, Slack, Discord. (#1995) Thanks @jackheuberger. - Docs: add Render deployment guide. (#1975) Thanks @anurag. +- CI: increase Node heap size for macOS checks. (#1890) Thanks @realZachi. ## 2026.1.24-3 From 159f6bfddd6c9e596856fdac65b775c67ed5c364 Mon Sep 17 00:00:00 2001 From: Shadow Date: Sun, 25 Jan 2026 21:02:18 -0600 Subject: [PATCH 13/13] macOS: bump Textual to 0.3.1 (#2033) Co-authored-by: Garric G. Nahapetian --- CHANGELOG.md | 1 + apps/macos/Package.resolved | 4 ++-- apps/shared/ClawdbotKit/Package.swift | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 93b171b38..19cea8844 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ Status: unreleased. - Skills: add missing dependency metadata for GitHub, Notion, Slack, Discord. (#1995) Thanks @jackheuberger. - Docs: add Render deployment guide. (#1975) Thanks @anurag. - CI: increase Node heap size for macOS checks. (#1890) Thanks @realZachi. +- macOS: avoid crash when rendering code blocks by bumping Textual to 0.3.1. (#2033) Thanks @garricn. ## 2026.1.24-3 diff --git a/apps/macos/Package.resolved b/apps/macos/Package.resolved index ffc524d1c..ef9609649 100644 --- a/apps/macos/Package.resolved +++ b/apps/macos/Package.resolved @@ -123,8 +123,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/gonzalezreal/textual", "state" : { - "revision" : "a03c1e103d88de4ea0dd8320ea1611ec0d4b29b3", - "version" : "0.2.0" + "revision" : "5b06b811c0f5313b6b84bbef98c635a630638c38", + "version" : "0.3.1" } } ], diff --git a/apps/shared/ClawdbotKit/Package.swift b/apps/shared/ClawdbotKit/Package.swift index 076842fce..88dc28b5c 100644 --- a/apps/shared/ClawdbotKit/Package.swift +++ b/apps/shared/ClawdbotKit/Package.swift @@ -15,7 +15,7 @@ let package = Package( ], dependencies: [ .package(url: "https://github.com/steipete/ElevenLabsKit", exact: "0.1.0"), - .package(url: "https://github.com/gonzalezreal/textual", exact: "0.2.0"), + .package(url: "https://github.com/gonzalezreal/textual", exact: "0.3.1"), ], targets: [ .target(