39claw is a Discord bot that connects your Discord conversations to Codex.
It is built for teams or individuals who want to work with Codex from inside Discord without inventing their own thread-routing rules. You mention the bot, 39claw decides which Codex thread should receive the turn, and the reply comes back into the same channel.
- mention-based conversation in Discord
- two conversation modes for different workflows
- one instance-specific slash command with explicit action choices
- SQLite-backed continuity across restarts
- image attachment support for guild mentions and direct messages
- queued acknowledgments when the same conversation is already busy
Install the macOS cask from the custom tap.
brew tap hatsunemiku3939/homebrew-tap
brew install --cask 39clawYou can also install it without a separate tap step.
brew install --cask hatsunemiku3939/homebrew-tap/39clawThe cask installs an unsigned binary. If macOS blocks execution, inspect the binary first and then remove the quarantine attribute manually.
xattr -dr com.apple.quarantine "$(brew --prefix)/Caskroom/39claw/<version>/39claw"Release assets for Linux include both .deb and .rpm packages for amd64 and arm64.
Download the package that matches your distribution and CPU architecture from the GitHub Releases page before installing it locally.
Install a Debian package:
sudo dpkg -i ./39claw_<version>_amd64.debInstall an RPM package:
sudo rpm -i ./39claw-<version>-1.x86_64.rpmDownload the archive that matches your platform from the GitHub Releases page.
The first stable release is assumed to be v1.0.0, and the release archives follow this naming pattern:
39claw_v1.0.0_Linux_x86_64.tar.gz39claw_v1.0.0_Linux_arm64.tar.gz39claw_v1.0.0_Darwin_x86_64.zip39claw_v1.0.0_Darwin_arm64.zip39claw_v1.0.0_Windows_x86_64.zip39claw_v1.0.0_Windows_arm64.zip
After downloading the archive:
- extract it
- move the
39clawbinary somewhere on yourPATH - run
39claw versionto confirm the installed release version
Example for Linux amd64:
curl -LO https://github.com/HatsuneMiku3939/39claw/releases/download/v1.0.0/39claw_v1.0.0_Linux_x86_64.tar.gz
tar -xzf 39claw_v1.0.0_Linux_x86_64.tar.gz
chmod +x 39claw
sudo mv ./39claw /usr/local/bin/39claw
39claw versionExample for macOS arm64:
curl -LO https://github.com/HatsuneMiku3939/39claw/releases/download/v1.0.0/39claw_v1.0.0_Darwin_arm64.zip
unzip 39claw_v1.0.0_Darwin_arm64.zip
chmod +x 39claw
sudo mv ./39claw /usr/local/bin/39claw
39claw versionExample for Windows amd64 with PowerShell:
curl.exe -L -o 39claw_v1.0.0_Windows_x86_64.zip https://github.com/HatsuneMiku3939/39claw/releases/download/v1.0.0/39claw_v1.0.0_Windows_x86_64.zip
tar -xf 39claw_v1.0.0_Windows_x86_64.zip
.\39claw.exe versionIf you prefer to build from source instead of using a release archive:
go build -o ./bin/39claw ./cmd/39claw
./bin/39claw version39claw runs in exactly one mode per bot instance.
Use daily mode when you want a lightweight shared assistant for day-to-day work.
- messages route through one active shared generation per local date
/<instance-command> action:clearcan rotate the current day's shared generation to a fresh thread when the current one is idle- the next local date starts a fresh Codex thread automatically
- durable preferences and long-lived context can be projected into runtime-managed files under
AGENT_MEMORY/ - if you want visible turns to consult that memory, add the necessary guidance to your own
AGENTS.md - users do not need to create or switch tasks before talking
If you want visible turns to consult the projected memory files, add guidance like this to your own AGENTS.md:
# Daily Memory Guidance
If `AGENT_MEMORY/` exists in the current workspace, consult its durable memory files when they are relevant to the user's request.
- Read `AGENT_MEMORY/MEMORY.md` as the primary durable memory file.
- Read the most relevant dated note in `AGENT_MEMORY/YYYY-MM-DD.<generation>.md` when bridge context is useful.
- Prefer the latest explicit user instruction when it conflicts with stored memory.
- Treat `AGENT_MEMORY/` as durable context only. Do not treat it as a source of temporary TODO items or transient chat history.Use task mode when you want durable work streams that continue across multiple days.
- each user works through an explicit active task
- task names are immutable slug-style identifiers such as
release-bot-v1 - each task eventually runs in its own task-specific Git worktree
/<instance-command> action:task-*controls which task is active- normal conversation does not run until a task is selected, unless the message uses a one-shot
task:<name>override
- 39claw responds to direct messages without a mention, and to guild messages only when the bot is mentioned
- a qualifying message may contain text, images, or both
- if the qualifying trigger includes no text and no usable image, the bot stays silent
- replies are posted in the same channel as replies to the triggering message
- when a normal-message reply finishes successfully, 39claw adds a
✅reaction to the primary bot reply when Discord permissions allow it
Each bot instance registers exactly one slash command.
Its name comes from CLAW_DISCORD_COMMAND_NAME, so one instance might expose /release while another exposes /docs.
For every instance:
/<instance-command> action:help- show the supported command surface for the current bot instance
In daily mode, the same root command also supports:
/<instance-command> action:clear- rotate the shared same-day daily session to a fresh generation when the current one is idle
In task mode, the same root command also supports:
/<instance-command> action:task-current- show the active task
/<instance-command> action:task-list- list open tasks and mark the active one
/<instance-command> action:task-new task_name:<name>- create a task from a slug-style name and make it active
/<instance-command> action:task-switch task_name:<name>- switch the active task by name
/<instance-command> action:task-close task_name:<name>- close a task by name
In task mode, a normal message may also start with task:<name> to route only that message to another open task without changing the active task.
Examples:
task:release-bot-v1 fix the failing teststask:docs-cleanupfollowed by a newline and the rest of the message body
39claw runs one Codex turn at a time for a given conversation context.
- if another message arrives while that context is busy, the bot can queue up to five waiting messages
- queued messages receive a short acknowledgment immediately
- the final answer arrives later as a reply to the queued message
- if the queue is already full, the bot returns a retry-later response
Queued messages are held in memory, so they are lost if the bot process exits before they run.
Before you start, make sure you have:
- a Discord bot token
- the
codexexecutable available on the machine that runs 39claw - a writable SQLite file path
- a working directory that Codex should operate in
- a write-capable Codex workdir if you plan to use
dailymode, because 39claw managesAGENT_MEMORY/files there - a Git repository workdir if you plan to use
taskmode - Go installed if you plan to run from source
The recommended local-development workflow is:
- copy
.env.exampleto.env.local - replace every placeholder in
.env.local - copy
.envrc.exampleto.envrc - run
direnv allow - start 39claw without pasting secrets into shell history
.env.local, .envrc, and .direnv/ are ignored by Git in this repository.
Keep real Discord tokens, Codex API keys, and any other credentials only in those ignored files.
Checked-in examples must contain placeholders only.
If you do not use direnv, keep the same rule: load secrets from an ignored local file instead of tracked scripts or inline launch snippets.
Codex Installation Guide
39claw launches the local codex CLI, so install Codex before starting the bot.
Recommended installation options:
- install with npm
- install with Homebrew
- download a release binary from GitHub if you prefer a manual install
npm install -g @openai/codexbrew install --cask codexIf you do not want to use a package manager, download the correct archive for your platform from the Codex GitHub releases page and extract the binary.
After extraction, you will usually want to rename the binary to codex and place it somewhere on your PATH.
Run:
codexThe official quick start then recommends signing in with ChatGPT. Codex can also be used with an API key, but that requires additional setup on the Codex side.
Official references:
Discord Setup Guide
If this is your first time running a Discord bot, do this before the quick start.
- open the Discord Developer Portal
- create a new application
- add a Bot user to that application
- copy the bot token and store it in your ignored
.env.localfile asCLAW_DISCORD_TOKEN
In the bot settings, enable Message Content Intent.
39claw reads the content of guild mention-triggered messages and direct messages, so this intent is required for normal conversation in guild channels.
Under OAuth2, generate an invite URL for the bot.
Use these scopes:
botapplications.commands
applications.commands is required because 39claw registers one instance-specific root slash command such as /release.
Recommended permissions:
View ChannelsSend MessagesRead Message History
Useful optional permissions:
Add ReactionsEmbed LinksAttach Files
Add Reactions is required if you want 39claw to mark completed normal-message replies with a ✅ reaction.
Invite the bot to an existing Discord server that you control.
39claw does not create a new server for you. It connects to Discord and listens in the server where the bot has been installed.
For first-time setup, it is easiest to use one dedicated test server or one dedicated bot channel.
If you set CLAW_DISCORD_GUILD_ID, 39claw registers slash commands in that guild for faster testing. This is usually the best choice while you are still validating the setup.
- normal conversation is mention-only in guild channels and works without a mention in DMs
- slash commands are explicit and do not require a mention
- in
taskmode, users must create or switch to a task before normal conversation will run
If you want a guided setup instead of the condensed quick start, use the step-by-step example guides in example/README.md:
Pick one:
CLAW_MODE=dailyCLAW_MODE=task
If you choose task, CLAW_CODEX_WORKDIR must point to a Git repository with an origin remote.
39claw treats that checkout as the operator-visible source repository and creates a managed bare parent under CLAW_DATADIR/repos plus task-specific worktrees under CLAW_DATADIR/worktrees.
If startup finds a missing, non-Git, or no-origin task workdir, the bot exits with a clear configuration error before it connects to Discord.
The safe-default setup is to keep your local values in .env.local and load them through .envrc.
Start by copying .env.example to .env.local, then replace the placeholders with real local values.
These variables are required in .env.local:
CLAW_MODECLAW_TIMEZONECLAW_DISCORD_TOKENCLAW_DISCORD_COMMAND_NAMECLAW_CODEX_WORKDIRCLAW_DATADIRCLAW_CODEX_EXECUTABLE
CLAW_CODEX_HOME is optional and can point 39claw at a custom Codex home directory.
For Discord deployments, also set:
CLAW_CODEX_APPROVAL_POLICY=never
This is effectively required in 39claw today because the current Codex SDK integration does not expose an API surface for handling interactive approval requests.
If you set CLAW_CODEX_APPROVAL_POLICY to on-request, on-failure, or untrusted, the bot can reach approval-gated work without any supported way for 39claw to complete that approval interaction.
Recommended startup flow:
cp .env.example .env.local
cp .envrc.example .envrc
direnv allow
go run ./cmd/39claw version
go run ./cmd/39claw39claw stores its SQLite database at 39claw.sqlite inside CLAW_DATADIR.
If CLAW_DISCORD_GUILD_ID is set, 39claw registers commands in that guild for faster testing. If it is omitted, commands are registered globally.
39claw currently exposes one local CLI subcommand:
go run ./cmd/39claw version- print the build version string and exit without starting the Discord runtime
Regular startup still uses go run ./cmd/39claw with no subcommand.
Try one of these:
- mention the bot with a text prompt
- mention the bot with text plus an image
- mention the bot with only an image
If you are running in task mode, create a task first with /<your-command> action:task-new task_name:<name>.
Choose a routing-safe slug such as release-bot-v1, not a free-form label with spaces.
The first normal message for a new task may spend a moment preparing that task's dedicated worktree before Codex replies.
CLAW_MODEdailyortask
CLAW_TIMEZONE- the timezone used for daily rollover
CLAW_DISCORD_TOKEN- Discord bot token
CLAW_DISCORD_COMMAND_NAME- the unique root slash command name for this bot instance
CLAW_CODEX_WORKDIR- in
dailymode, the working directory passed to Codex; intaskmode, the operator-visible Git checkout that must have anoriginremote
- in
CLAW_DATADIR- directory used for local state;
39claw.sqlite, managed task repositories underrepos/, and task worktrees underworktrees/all live here
- directory used for local state;
CLAW_CODEX_EXECUTABLE- path to the
codexexecutable
- path to the
CLAW_DISCORD_GUILD_ID- register slash commands in one guild for faster iteration
CLAW_LOG_LEVEL- logging level, default
info
- logging level, default
CLAW_LOG_FORMAT- log format,
jsonby default, ortext
- log format,
CLAW_CODEX_MODEL- choose a Codex model
CLAW_CODEX_BASE_URL- override the Codex base URL
CLAW_CODEX_API_KEY- provide an API key when needed
CLAW_CODEX_HOME- when set, 39claw injects it into the Codex CLI process as
CODEX_HOME
- when set, 39claw injects it into the Codex CLI process as
CLAW_CODEX_SANDBOX_MODEread-only,workspace-write, ordanger-full-access
CLAW_CODEX_ADDITIONAL_DIRECTORIES- extra writable directories, separated by the OS path-list separator
CLAW_CODEX_SKIP_GIT_REPO_CHECKtrueorfalse
CLAW_CODEX_APPROVAL_POLICYnever,on-request,on-failure, oruntrusted- for 39claw today,
neveris effectively required because the current Codex SDK integration does not expose an approval-handling API surface
CLAW_CODEX_MODEL_REASONING_EFFORTminimal,low,medium,high, orxhigh
CLAW_CODEX_WEB_SEARCH_MODEdisabled,cached, orlive
CLAW_CODEX_NETWORK_ACCESStrueorfalse
39claw emits structured logs through log/slog.
The default output format is JSON so logs can be indexed directly by common log backends.
Set CLAW_LOG_FORMAT=text only when human-readable local debugging is more important than machine parsing.
High-value runtime events include:
queue_admission- fields include
outcome=execute_now|queued|queue_full, plusqueue_positionwhen queued
- fields include
codex_turn_started- fields include
thread_resumed,prompt_char_count,image_count, andworking_directory_set
- fields include
codex_turn_finished- fields include
outcome,latency_ms,thread_id, and token usage fields when Codex reports them
- fields include
queued_turn_startedandqueued_turn_finished- fields include
queue_wait_msand whether shutdown or reply delivery interrupted the queued flow
- fields include
deferred_reply_delivery- fields include
outcome=success|failure|dropped_on_shutdown
- fields include
Most message-path events also carry routing context such as component, mode, logical_key, channel_id, reply_to_id, user_id, and task_id when available.
Prefer the fake-runtime suites before running optional live Discord smoke checks.
The reusable harness lives under internal/testutil/runtimeharness, and the primary adapter-level coverage lives under internal/runtime/discord.
For focused runtime behavior validation from the repository root, run:
go test ./internal/testutil/runtimeharness -v
go test ./internal/runtime/discord -run 'TestRuntimeContract' -vThese suites exercise the real Discord runtime startup path with a fake session and verify observable behavior such as:
- reply targeting for qualifying guild mentions and direct messages
- streamed in-place reply edits for immediate turns
- queued acknowledgments followed by deferred replies
- command-style interaction presentation
- attachment-aware message handling without a live Discord deployment
Use this checklist when you want optional live Discord hardening beyond the normal automated suites. It is most useful for real-platform behaviors such as hosted attachments, command registration, permissions, and final reply delivery:
- mention the bot in a guild channel and confirm the reply targets the original message
- confirm the bot adds a
✅reaction after the final normal-message reply finishes - send the bot a direct message without a mention and confirm it answers
- mention the bot with text plus an image and confirm the request is handled
- mention the bot with only an image and confirm the bot still answers
- send unrelated chatter without a mention and confirm the bot stays silent
- run
/<your-command> action:helpand confirm it matches the configured mode - in
taskmode, run/<your-command> action:task-new task_name:smoke-testand confirm the response is ephemeral - send overlapping messages for the same conversation and confirm the later one is queued
- send a long Codex response and confirm the bot splits it into readable Discord-safe chunks
39claw now has a first-stage tag-driven release path for the production 39claw binary.
The flow is intentionally conservative:
- release tags are created manually
- GitHub Actions builds the release from a pushed
v*tag - GoReleaser creates a draft GitHub Release instead of publishing immediately
- Linux
.deband.rpmpackages are attached to the draft release - the macOS Homebrew cask is updated in the
HatsuneMiku3939/homebrew-taprepository
Before tagging, run the local validation commands from the repository root:
make test
make lint
go vet ./...
make release-check
make release-snapshotAfter the release candidate gate passes, create and push a tag such as:
git tag -a v0.1.0 -m "v0.1.0"
git push origin v0.1.0Pushing a tag that starts with v triggers the GitHub Actions release workflow and creates a draft GitHub Release with cross-platform archives for 39claw.
The same release workflow also publishes Linux package artifacts and updates the Homebrew cask in the HatsuneMiku3939/homebrew-tap repository. Set the HOMEBREW_TAP_GITHUB_TOKEN GitHub Actions secret to a token with write access to that repository before pushing a release tag.
For the complete release candidate checklist, tagging steps, and post-release verification flow, use RELEASE_RUNBOOK.md.
39claw is still early-stage software, but the current build already supports:
- guild mention and direct-message normal conversation
- guild-mention and direct-message image attachment intake
dailyandtaskconversation modes- one instance-specific root slash command
action:help,action:task-current,action:task-list,action:task-new,action:task-switch, andaction:task-close- SQLite-backed persistence for thread bindings and task state
- streamed in-place progress updates for immediate Discord turns
- queued acknowledgments with deferred follow-up replies
For deeper detail, start here:
