Run Codex on your own machine from Feishu/Lark or Weixin chat.
Chat2Codex turns a chat bot into a message platform for the local Codex CLI. Send prompts, files, and images; receive progress and final answers; approve Codex actions; and resume local Codex threads without a public webhook server.
- Production adapters are Feishu/Lark long connection and native Weixin
ClawBot iLink long polling. Select exactly one per process with
CHAT2CODEX_ADAPTER=feishu|weixin; the omitted selector remainsfeishu. See Architecture. - Direct-message routing is enabled by default, but every sender or direct chat
must be explicitly allowlisted except for
/whoamidiscovery. - Group chats are disabled by default and require both chat and sender
allowlists. They can be constrained to
CODEX_GROUP_ALLOWED_ROOTS. - The Codex app-server protocol is experimental. Run
chat2codex doctorand the checks in Codex App-Server Guardrails after installing or upgrading Codex CLI.
- Node.js
>= 20.12.0 - npm for installing the package.
- Codex CLI installed and logged in on the machine running this bridge.
- Either a Feishu/Lark account that can create a bot app, or a personal Weixin account with the ClawBot entry enabled.
- The app needs message receive/send/resource permissions, long-connection
event subscriptions for message events, and the
card.action.triggercallback.
npm install -g chat2codexCreate and connect a Feishu/Lark app automatically by scanning a QR code:
chat2codex setup --workdir /absolute/path/to/your/repoThe setup command renders a terminal QR code and keeps the authorization URL as
a fallback. Scan it with Feishu/Lark, confirm the app creation, and it writes
FEISHU_APP_ID, FEISHU_APP_SECRET, LARK_DOMAIN, and the scanning user's
open_id in ALLOWED_USER_IDS to
~/.chat2codex/.env. You can still run
chat2codex init --workdir /absolute/path/to/your/repo and edit that env file
manually if you already have an app. After the first start, /whoami remains
available for discovering ids that need to be allowlisted.
Check the local setup, then start the bridge:
chat2codex doctor
chat2codex startFor Weixin, connect Chat2Codex directly to ClawBot instead:
chat2codex setup weixin --workdir /absolute/path/to/your/repoThe QR flow handles confirmation, numeric verification, IDC redirects, and
expiry refresh. It writes CHAT2CODEX_ADAPTER=weixin, the work directory, and
the scanning user's stable iLink id to the env allowlist. Bot Token, Bot ID,
API base URL, and user ID are stored with mode 0600 in
~/.chat2codex/weixin/credentials.json, never in .env. This route uses the
native iLink HTTP protocol; OpenClaw is not installed or run.
The machine still has to stay powered on, online, and running the Chat2Codex
service; ClawBot is only the transport and does not run another agent or model.
Send a DM to the bot:
/status
Summarize this repository.
You can also send a file or image. Chat2Codex downloads supported attachments
under ATTACHMENT_DOWNLOAD_DIR and appends their local paths to the Codex
prompt. If the message contains only an attachment, it uses a default prompt
asking Codex to inspect that file or image.
During a run, Chat2Codex adds a processing reaction below the original message
and sends throttled plain-text progress at most once every 30 seconds. The
processing reaction is removed when the run ends; failures receive a failure
reaction. The final Codex response is sent as a rendered rich-text post. Send
/stop to abort the active run and /retry to re-run the latest remembered
prompt. Run details remain available through /summary, /files, /diff, and
/logs. If message reactions are unavailable, Chat2Codex falls back to a
plain-text acknowledgement.
| Command | Effect |
|---|---|
/help |
Show a compact mobile guide to the main Chat2Codex commands. |
chat2codex / chat2codex start |
Start the adapter selected by CHAT2CODEX_ADAPTER. |
chat2codex setup --workdir <path> |
Create/connect a Feishu/Lark app and write .env. |
chat2codex setup weixin --workdir <path> |
Scan and connect a Weixin ClawBot and write private credentials plus .env. |
chat2codex init --workdir <path> |
Create a starter .env when you already have an app. |
chat2codex doctor |
Check .env, Node.js, Codex CLI and protocol-snapshot versions, workspace paths, and mobile/team-bot safety warnings. |
chat2codex smoke [--mode turn|approval] |
Verify the Codex app-server protocol locally. |
chat2codex service print|install|uninstall |
Manage a user-level launchd/systemd service. |
By default, Chat2Codex stores configuration and runtime state under
~/.chat2codex. Set CHAT2CODEX_HOME=/path/to/home or pass --env /path/to/.env
when you need a separate bot instance.
- Feishu/Lark long connection or native Weixin ClawBot long polling; neither requires a public webhook server.
- One reusable Codex app-server session per chat/thread scope. Consecutive turns keep the same process—and therefore session-scoped grants—while the sender, cwd, thread, policy, and session epoch remain unchanged.
/help,/status,/host,/projects,/project <index|path>,/threads,/history,/search,/resume,/fork,/archive,/archived,/unarchive,/retry,/usage,/service status|logs|restart,/compact,/plan <task>,/new,/cd <path>,/stop,/steer,/answer,/mcp-answer,/approve,/permit,/mcp-decide,/summary,/files,/diff,/logs, and/whoamicommands.- Local state in JSON.
- Codex app-server JSON-RPC for machine-readable progress, final output, and approval callbacks.
- A processing reaction on the original message, throttled plain-text progress,
and a failure reaction when a run fails.
/stop,/retry, and the run-detail commands remain ordinary text commands. - Feishu/Lark approval cards for Codex command/file-change approval requests. Buttons are generated from Codex's current approval decisions, including Approve, Approve session, Deny, and Cancel turn when those options are offered.
/plan <task>runs one turn in Codex Plan mode. Structured CodexrequestUserInputquestions are rendered as sender-bound cards, with an explicit/answer <reply-code> <value>fallback for free-form input. Options are validated against the original request; secret questions fail closed instead of collecting credentials through chat history.- Standard MCP form and URL elicitations rendered as sender-bound cards. Typed
form fields can also use
/mcp-answer <reply-code> <JSON-quoted-field-id> <value>; values are validated against the original schema, while sensitive fields fail closed. - Additional-permission requests from
item/permissions/requestApprovalrendered as complete-profile approval cards. Chat2Codex exposes only deny, turn-scoped grant, and session-scoped grant; a grant always returns the original profile requested by Codex. - Text-only adapters receive sender-bound, expiring eight-character reply codes:
/approve <code> <number>maps only to the original Codex decision array,/permit <code> <deny|turn|session>maps only to the three permission decisions, and/mcp-decide <code> <accept|decline|cancel>handles MCP URL decisions. - Weixin v1 is deliberately direct-message only. It accepts text, quoted text, inbound images, and inbound files; decrypts official CDN media with AES-128-ECB; maps processing reactions to typing; and drops groups, voice/video, and unsupported media with diagnostics. Outbound media and in-place message updates are not supported.
- Feishu/Lark image and file messages downloaded to local paths and passed to Codex with the prompt.
- Event diagnostics in logs and
/statusfor recent routed/dropped messages. - Operational
/statusdetails for queue depth, active run age, approval wait age, and recent failures. /hosthealth card for the bridge host, Codex binary, default cwd, queue, active run count, approval wait count, and mobile/team-bot safety warnings.- Runtime steering with
/steer <instruction>for sending follow-up guidance to the active Codex turn without waiting behind the chat queue. - Search, history, fork, and compact controls for Codex app-server threads from chat, so mobile users can continue older work without returning to the host.
- Optional run and approval timeouts for unattended team bot deployments.
- Team-bot friendly error summaries when Codex fails or cannot start.
- Final Codex replies rendered as Feishu/Lark rich-text posts.
- User-level launchd/systemd setup for long-running team deployments.
Chat2Codex uses the experimental codex app-server --stdio protocol for
thread control, progress events, and approval callbacks. After installing or
upgrading Codex CLI, first run:
chat2codex doctordoctor compares the exact detected codex --version output with the Codex
version recorded in the bundled protocol snapshot. A mismatch, or a missing or
unreadable snapshot manifest, is a compatibility warning rather than a failed
doctor run. Treat it as unverified protocol compatibility and continue with the
fast local smoke test:
chat2codex smokeThis validates initialize and thread/start against a temporary workspace
without starting a model turn. To verify a full model-backed turn as well:
chat2codex smoke --mode turnHistorical-turn forks use a stricter compatibility gate. /fork --turn stops
before sending thread/fork unless the running app-server version exactly
matches the bundled protocol snapshot. Older servers may ignore lastTurnId
and silently create a whole-thread fork; ordinary /fork keeps its existing
compatibility behavior.
To verify a real command-approval request, run:
chat2codex smoke --mode approvalThat mode uses a temporary workspace, approvalPolicy=untrusted, and
sandbox=workspace-write. It prompts Codex to create approval-smoke.txt,
requires the app-server to emit item/commandExecution/requestApproval, returns
accept, waits for turn/completed, and checks the file content.
The current generated protocol snapshot lives under
docs/codex-app-server-protocol. Refresh it
after Codex CLI upgrades:
bun run protocol:generate
git diff -- docs/codex-app-server-protocolReview schema diffs before changing
src/agent/codex-runner.ts. Chat2Codex handles only
explicitly supported app-server server requests. Unknown methods return a
JSON-RPC method-not-found error; malformed approval requests return an
invalid-params error without exposing or inventing an approval option.
item/tool/requestUserInput is supported for /plan <task> turns through
cards and explicit /answer replies; request fields and callback answers are
revalidated, withdrawn requests reject late replies, and isSecret questions
fail closed because chat cannot guarantee masked input. Standard
mcpServer/elicitation/request form and
URL requests are supported through cards, with /mcp-answer for typed form
values. The bridge does not persist or echo requestUserInput or MCP answer
values, and sensitive form fields fail closed. The OpenAI-specific MCP form
extension is not negotiated.
item/permissions/requestApproval is also supported through a sender-bound
card that discloses the complete requested permission profile. The only bridge
decisions are deny, grantTurn, and grantSession. A grant clones the exact
original profile held by the runner; card payloads cannot replace permissions
or enable strictAutoReview. Malformed, incomplete, withdrawn, or undisplayable
requests fail closed.
When CODEX_APPROVAL_POLICY allows interactive approvals, Codex app-server
emits approval requests while a turn is running. Chat2Codex posts a separate
approval card to the same chat and pauses Codex until an authorized user clicks
one of the options. The card buttons mirror Codex's availableDecisions for
command execution requests. The current file-change approval request does not
include target files or patch details, so Chat2Codex exposes only decline/cancel
until those details can be correlated and rendered completely. If command
decisions are absent or null, the bridge likewise exposes only decline/cancel;
a malformed decision list is rejected as invalid params.
Cards disclose additional filesystem/network permissions and every exact
exec/network policy rule. If security-relevant details cannot be rendered
completely, allow actions are removed and only decline/cancel remain; the
message router enforces the same decision filter when processing card callbacks.
Apps created with the current chat2codex setup flow include that callback.
If you created the Feishu/Lark app before interactive approvals and input cards
were added, manually subscribe the card.action.trigger callback in the
developer console so those card decisions can reach this bridge over the long
connection.
If you created the app before attachment support was added, also grant the
message resource/read permission used by Feishu/Lark's
im.v1.messageResource.get API; otherwise attachment downloads will fail even
though text messages still work.
Group messages are ignored unless they explicitly mention the bot. To enable a
group chat, first send @Chat2Codex /whoami in that chat and copy the reported
chat_id. Then set:
ALLOW_GROUPS=true
ALLOWED_CHAT_IDS=oc_xxxGroup /whoami replies expose only chat_id, chat type, and the access
decision. Sender ids are intentionally omitted from group history; use a direct
message /whoami when an administrator needs the sender's available ids.
For a team group, keep the bot allowlisted and run it as a user-level background
service instead of leaving chat2codex start in a terminal.
-
In the target group, send
@Chat2Codex /whoamiand copy the reportedchat_id. -
Update
~/.chat2codex/.env:ALLOW_GROUPS=true ALLOWED_CHAT_IDS=oc_xxx ALLOWED_USER_IDS=ou_xxx,ou_yyy CODEX_WORKDIR=/absolute/path/to/your/repo CODEX_GROUP_ALLOWED_ROOTS=/absolute/path/to/your/repo,/absolute/path/to/team/repos CODEX_BIN=/absolute/path/to/codex CODEX_APPROVAL_POLICY=on-request CODEX_RUN_TIMEOUT_MS=1800000 CODEX_APPROVAL_TIMEOUT_MS=300000 ATTACHMENT_DOWNLOAD_DIR=/absolute/path/to/chat2codex-attachments
CODEX_BINshould be absolute for background services because launchd and systemd do not load your interactive shell startup files. UseCODEX_APPROVAL_POLICY=neverfor unattended bots that should never wait for a Feishu/Lark approval click. LeaveCODEX_RUN_TIMEOUT_MS=0andCODEX_APPROVAL_TIMEOUT_MS=0to disable automatic timeouts; set positive millisecond values when team bots should fail fast instead of waiting forever. -
Preview the service file:
chat2codex service print
-
Install the user service:
chat2codex service install
On macOS this installs a launchd agent named
com.chat2codex.bridge. On Linux this installs a systemd user service namedchat2codex.service. Re-runchat2codex service installafter upgrading from an older release to install the supervisor marker required by/service restart.
Useful service commands:
# macOS status and logs
launchctl print gui/$(id -u)/com.chat2codex.bridge
tail -f ~/.chat2codex/.data/logs/chat2codex.out.log ~/.chat2codex/.data/logs/chat2codex.err.log
# Linux status and logs
systemctl --user status chat2codex
journalctl --user -u chat2codex -f
# Uninstall the user service
chat2codex service uninstallThe default launchd log directory is ~/.chat2codex/.data/logs for both npm
installs and source checkouts. Foreground development with bun run dev logs to
the terminal instead. File logging uses bounded entries and rotates according
to LOG_FILE_MAX_BYTES and LOG_FILE_MAX_FILES. A source checkout that
intentionally wants project-local service logs can override them explicitly:
bun src/index.ts service install --env .env --project-dir . \
--stdout .data/logs/chat2codex.out.log \
--stderr .data/logs/chat2codex.err.log| Command | Effect |
|---|---|
/status |
Show current chat session, cwd, queue depth, active run age, approval wait age, recent failures, attachment directory, and recent event diagnostics. |
/host |
Send a Host health card with Codex CLI availability, service paths, queue/approval counts, and mobile/team-bot safety warnings. /health is an alias. |
/projects |
List projects discovered from Codex app-server threads, grouped by cwd. |
/project <index|path> |
Enter a listed project by number, or switch to a directory path, and start with no selected thread. |
/threads |
List recent Codex conversations for the current project. /sessions is an alias. |
/history [index|turn_id] |
Show recent turns for the current conversation; pass a listed number or turn id to show turn details. |
/search <term> |
Search Codex conversation history and save results for /resume <index> or /fork <index>. |
/resume <index|thread_id> |
Continue a listed conversation by number, or load one directly by Codex thread id. |
/fork [index|thread_id] |
Fork the current, listed, or specified Codex conversation and switch this chat to the new thread. |
/fork --turn <history-index|turn_id> |
Fork the current conversation through a selected non-running turn. Use an index from /history or pass a turn id directly; the source thread stays unchanged and local files are not restored. |
/retry |
Retry the latest task remembered in this bridge process for the same chat and original sender. Restarting the bridge clears this exact-prompt retry context. |
/usage |
Show the latest turn and cumulative thread token usage plus context-window occupancy when Codex provides it. |
/archive |
Archive the currently selected Codex thread and clear it from the chat without changing local files. |
/archived |
List archived threads for the current project. |
/unarchive <archived-index|thread_id> |
Restore an archived thread. Use /threads and /resume afterward to continue it. |
/service status |
Show bridge PID, uptime, queue state, restart availability, and log source. |
/service logs |
Show a bounded recent service-log tail. Direct message plus an explicit ALLOWED_USER_IDS match is required. |
/service restart |
Gracefully restart a supervisor-managed bridge. Uses the same admin boundary as logs and refuses while work is active or queued. |
/compact |
Request compaction for the current Codex conversation. |
/plan <task> |
Run one task in Codex Plan mode. Use this mode when Codex should call request_user_input; the next ordinary message returns to Default mode. |
/new |
Start a fresh Codex conversation in the current project. |
/cd <path> |
Change the current chat cwd and start a fresh Codex thread. |
/stop |
Stop the active Codex run for the current chat. This command bypasses queued chat work. |
/steer <instruction> |
Send extra guidance to the active Codex run immediately, bypassing queued chat work. |
/answer <reply-code> <value> |
Answer the current non-secret Codex requestUserInput question. The reply code is shown on the question card; answers bypass queued chat work and are neither persisted nor echoed by the bridge. |
/mcp-answer <reply-code> <JSON-quoted-field-id> <value> |
Answer the current non-sensitive MCP form field using the exact command shown on its card. /skip skips an optional field; quote the value as "/skip" when that literal string is intended. Typed values are checked against the original schema; answers bypass queued chat work and are neither persisted nor echoed by the bridge. |
/approve <reply-code> <option-number> |
Resolve a Codex command/file approval on text-only adapters; the number maps only to the original decision array. |
/permit <reply-code> <deny|turn|session> |
Deny additional permissions or grant them for the current turn/session. |
/mcp-decide <reply-code> <accept|decline|cancel> |
Resolve an MCP URL request on text-only adapters. |
/summary |
Show the most recent run summary for this chat. |
/files |
Show changed files from the most recent run. |
/diff |
Show the latest captured diff from the most recent run. |
/logs |
Show command summaries and captured output previews from the most recent run. |
/whoami |
Show the current chat_id, chat type, and access decision; sender ids are included only in direct messages. |
Chat2Codex defaults to CODEX_SANDBOX=workspace-write, so Codex can edit inside the selected workspace. Use read-only for safer Q&A-only bots.
It defaults to CODEX_APPROVAL_POLICY=never for unattended operation. Set
CODEX_APPROVAL_POLICY=on-request or untrusted when you want Codex approval
requests to appear as Feishu/Lark cards. In group chats, approval buttons can
only be handled by users listed in ALLOWED_USER_IDS.
CODEX_RUN_TIMEOUT_MS=0 and CODEX_APPROVAL_TIMEOUT_MS=0 disable automatic
timeouts. For long-running background bots, set positive millisecond values so a
stuck Codex turn or unattended approval request is cancelled and recorded in
/status as a recent failure with a recovery hint.
Key production resource paths are bounded by default and can be tuned with the
positive-integer settings in .env.example:
CODEX_MAX_CONCURRENT_RUNS, CODEX_MAX_APP_SERVER_SESSIONS, the global and per-chat
BRIDGE_MAX_PENDING_MESSAGES / BRIDGE_MAX_PENDING_MESSAGES_PER_CHAT limits,
which bound active jobs, undelivered durable replies, pending control messages,
and in-turn approval/input waits,
attachment count/file/message/store quotas plus ATTACHMENT_RETENTION_HOURS,
chat/stderr/run-log/diff output limits, log entry/file rotation limits, and
terminal job/delivered-outbox retention counts.
Active jobs and undelivered outbox entries are never removed by retention
pruning. Attachments are streamed into private temporary files, checked against
the configured quotas, atomically finalized, and lazily expired without
following symlinks.
Reusable app-server sessions are held only in memory. Idle sessions expire
after CODEX_APP_SERVER_IDLE_TTL_MS (15 minutes by default), and the least
recently used idle session is closed when CODEX_MAX_APP_SERVER_SESSIONS is
reached. Changing the sender identity, cwd, thread, policy, /new epoch, or
using /fork//compact closes the old owner before another process can attach.
Messages without a stable sender id use a single-turn process and cannot inherit
session-scoped grants. A graceful SIGINT/SIGTERM closes the Lark connection
and all Codex children; queued durable work remains recoverable, while running
work is marked interrupted and is never replayed automatically.
chat2codex doctor and /host both surface mobile/team-bot safety warnings,
including relative CODEX_BIN, group chats without ALLOWED_USER_IDS,
disabled run/approval timeouts, and high-risk sandbox settings.
If a chat's selected cwd is deleted, an ENOENT startup failure is treated as
a missing workspace rather than a missing Codex binary. Chat2Codex clears the
selected thread, switches the chat back to CODEX_WORKDIR when that directory
is still allowed, and asks the user to resend the task. If the default cwd is
also unavailable, use /cd <existing-directory> before retrying.
Direct-message routing is enabled by default, but messages other than /whoami
must come from a sender in ALLOWED_USER_IDS or a direct chat in
ALLOWED_CHAT_IDS. Group messages must mention the bot and require all three:
ALLOW_GROUPS=true, an allowed chat, and a sender in ALLOWED_USER_IDS.
Authorized direct messages can switch to any local directory. Group chats are
constrained to canonical paths under CODEX_GROUP_ALLOWED_ROOTS, or under
CODEX_WORKDIR when that list is empty; symlinks cannot bypass the boundary.
Incoming events are persisted to a pending inbox before the long-connection
handler returns. Codex prompts also create durable jobs before execution. A
queued job can resume after restart; a job that had reached running is marked
interrupted and is not automatically executed again because its side effects
cannot be inferred safely. Terminal replies are delivered from a durable outbox
with stable idempotency keys, so a chat-delivery failure does not rerun Codex.
After restart, read-only control messages such as /status may be replayed;
mutating or run-targeted controls such as /new, /stop, and /steer are not
replayed onto another task. A message previously classified as non-Codex cannot
be promoted into a Codex run solely because access or routing configuration
changed during restart.
Codex runs targeting the same workspace are serialized across chats, while
different workspaces can run in parallel up to CODEX_MAX_CONCURRENT_RUNS;
global and per-chat queue admission is rejected before Codex starts when its
configured limit is reached. /status and /stop also cover runs waiting for a
workspace or global permit. Only one bridge process may use a given
BRIDGE_STATE_PATH. Keep approvals and Git review enabled for sensitive work,
and inspect the thread/worktree before manually retrying an interrupted job.
The instance lock is removed on normal shutdown. A SIGKILL or fatal runtime
crash can leave <BRIDGE_STATE_PATH>.lock; after confirming that no bridge
process is running, remove that lock directory before restarting. It is never
reclaimed automatically because doing so can race with another startup.
Do not run this bot in a group with untrusted people while using broad filesystem access. A chat bot that can drive a local coding agent is effectively a remote control surface for your machine.
The npm package runs the built Node.js ESM entrypoint. For local development from a source checkout, use Bun:
bun install
bun run devUse bun run start:bun only after validating the Feishu SDK long-connection path in your environment.
Chat2Codex is an unofficial project and is not affiliated with OpenAI.
bun run checkThis runs application and adapter-contract type checking, the Bun test suite,
architecture-boundary checks, and the production build. Use bun audit to
check the Bun dependency lockfile.
See CONTRIBUTING.md for local development and pull request guidance. See SECURITY.md before running Chat2Codex in a shared chat or reporting a security issue.
- Design and validate safe Weixin outbound media, additional message types, and controlled group-chat support.
- Optionally move adapters behind an external gateway when deployments need process-level credential and SDK isolation.