Bohan Jiang a67bf81225 fix(agent): surface host OAuth token via env var on macOS isolation (MUL-2603) (#3267)
* fix(agent): surface host OAuth token via env var on macOS isolation (MUL-2603)

Claude Code 2.x scopes the macOS keychain credentials entry by
sha256(CLAUDE_CONFIG_DIR)[:8], so the MUL-2603 isolation path strands
the child at "Not logged in" even after #3261 mirrored .claude.json:
the child looks up `Claude Code-credentials-<scratch-hash>`, the host
token is sitting in the no-suffix `Claude Code-credentials` entry.

Read the host OAuth token from the keychain via /usr/bin/security and
inject it as CLAUDE_CODE_OAUTH_TOKEN, which bypasses keychain lookup
entirely. Linux/Windows continue to use the .credentials.json mirror
(no-op there). Operator-pinned tokens and ANTHROPIC_API_KEY both take
precedence over the keychain reader.

Co-authored-by: multica-agent <github@multica.ai>

* fix(agent): tighten empty-value auth gate, pin Claude CLI env-scrub assumption (MUL-2603)

Empty-value gate
  - `ANTHROPIC_API_KEY=` inherited from a login shell that conditionally
    exports auth previously posed as an "operator pinned API-key auth"
    choice and disabled the keychain reader, stranding the isolated child
    at "Not logged in" even though no auth was actually selected.
  - Custom_env `CLAUDE_CODE_OAUTH_TOKEN=""` (stale agent config) had the
    same effect, plus would have shadowed a keychain-injected token in
    libc env lookups that pick the first match.
  - Both are now treated as noise: the empty entry is dropped from the
    child env and the keychain reader runs unchanged. Two new unit tests
    cover the os.Environ side (`...TreatsEmptyAnthropicAPIKeyAsUnpinned`,
    `...HonorsNonEmptyAnthropicAPIKey`) and the custom_env side
    (`...EmptyOAuthTokenInCustomEnvAsUnpinned`).

Env-scrub boundary
  - Surfacing `CLAUDE_CODE_OAUTH_TOKEN` to the isolated child is only
    safe because Claude Code itself drops that variable from the env it
    hands to Bash / hook subprocesses, so a model-driven `printenv` can
    never echo the secret into the agent transcript.
  - Empirically verified against `claude` 2.1.121:
        printf '...test -n "$CLAUDE_CODE_OAUTH_TOKEN" && echo SET || echo UNSET...' \
            | CLAUDE_CODE_OAUTH_TOKEN=sk-canary-XYZ \
              MUL2603_CONTROL=control-value \
              claude --print --output-format text \
                     --allow-dangerously-skip-permissions --allowedTools Bash
    returned `UNSET` for the OAuth token while the non-sensitive
    `MUL2603_CONTROL` control returned `CONTROL-SET`, proving the CLI
    scrubs only the auth env, not the env in general.
  - Pinned this assumption in a new skip-gated regression test
    (`TestClaudeCLIScrubsOAuthTokenFromBashSubprocess`) that boots the
    real CLI with a canary token; failing the test means upstream
    Claude Code stopped scrubbing and the passthrough must move off env
    vars before MUL-2603 can ship.

Co-authored-by: multica-agent <github@multica.ai>

* fix(agent): gate keychain passthrough on default host dir, harden scrub test (MUL-2603)

Two follow-ups from the round-2 review on #3267:

1. Custom CLAUDE_CONFIG_DIR no longer pulls the default OAuth token.
   Claude Code 2.x maps each config dir to its own suffixed
   `Claude Code-credentials-<hash>` keychain entry, so an operator that
   pins a managed/custom CLAUDE_CONFIG_DIR via custom_env or the
   daemon-host env was getting the *daemon user's* default unsuffixed
   entry injected into the isolated child — silently crossing accounts,
   exactly the boundary mirrorHostClaudeJSONIfMissing already protects
   for `.claude.json`. buildClaudeEnvWith now threads the effective
   hostConfigDir through and only calls the reader when that dir is the
   default `$HOME/.claude`. The new gate has a unit-level truth table
   (TestIsDefaultHostClaudeConfigDir) plus a regression
   (TestBuildClaudeEnvIsolatedSkipsKeychainForCustomHostConfigDir) that
   makes a t.Fatal-armed reader prove the gate keeps the read off for
   custom dirs.

2. Scrub e2e now asserts the control prong and the proof-of-execution
   marker, not just "canary absent". The previous assertion would
   false-pass on a model refusal, paraphrase, or "Bash gets no env at
   all" upstream change. The strengthened version sets a non-secret
   MUL2603_CONTROL alongside the canary OAuth token and asserts (a)
   canary is NOT in the transcript, (b) CONTROL-SET IS in the
   transcript (env propagation works for non-secrets — proves a
   targeted scrub), (c) UNSET IS in the transcript (the Bash tool
   actually ran AND saw the OAuth var as empty/unset). Code comment in
   buildClaudeEnvWith and the test docstring now narrow the
   security contract to the Bash tool subprocess only; hook subprocess
   env-scrub is no longer claimed because it has not been verified.

Co-authored-by: multica-agent <github@multica.ai>

* test(agent): use per-run nonces in Claude scrub e2e to kill false-pass (MUL-2603)

Elon's round-3 review flagged that TestClaudeCLIScrubsOAuthTokenFromBashSubprocess
still false-passed: the proof markers "UNSET" / "CONTROL-SET" were literal
strings in the prompt, so strings.Contains matched them even when the model
only paraphrased the prompt without spawning Bash.

Replace the hard-coded markers with two per-run random hex nonces passed *only*
via env vars (MUL2603_UNSET_NONCE, MUL2603_CONTROL_NONCE). The prompt now
references the variable names, not the values, so the nonces can land in the
transcript only if a real Bash subprocess inherits the env vars and echoes
them. A paraphrasing or refusing model cannot fake nonces it never saw.

Also update the security-boundary comment in buildClaudeEnvWith to describe
the nonce-based proof.

Co-authored-by: multica-agent <github@multica.ai>

---------

Co-authored-by: multica-agent <github@multica.ai>
2026-05-26 15:29:58 +08:00

Multica — humans and agents, side by side

Multica

Multica

Your next 10 hires won't be human.

The open-source managed agents platform.
Turn coding agents into real teammates — assign tasks, track progress, compound skills.

CI GitHub stars

Website · Cloud · X · Self-Hosting · Contributing

English | 简体中文

What is Multica?

Multica turns coding agents into real teammates. Assign issues to an agent like you'd assign to a colleague — they'll pick up the work, write code, report blockers, and update statuses autonomously.

No more copy-pasting prompts. No more babysitting runs. Your agents show up on the board, participate in conversations, and compound reusable skills over time. Think of it as open-source infrastructure for managed agents — vendor-neutral, self-hosted, and designed for human + AI teams. Works with Claude Code, Codex, GitHub Copilot CLI, OpenClaw, OpenCode, Hermes, Gemini, Pi, Cursor Agent, Kimi, and Kiro CLI.

For larger teams, Squads add a stable routing layer: assign work to a group led by an agent, and the leader delegates to the right member.

Multica board view

Why "Multica"?

Multica — Multiplexed Information and Computing Agent.

The name is a nod to Multics, the pioneering operating system of the 1960s that introduced time-sharing — letting multiple users share a single machine as if each had it to themselves. Unix was born as a deliberate simplification of Multics: one user, one task, one elegant philosophy.

We think the same inflection is happening again. For decades, software teams have been single-threaded — one engineer, one task, one context switch at a time. AI agents change that equation. Multica brings time-sharing back, but for an era where the "users" multiplexing the system are both humans and autonomous agents.

In Multica, agents are first-class teammates. They get assigned issues, report progress, raise blockers, and ship code — just like their human colleagues. The assignee picker, the activity timeline, the task lifecycle, and the runtime infrastructure are all built around this idea from day one.

Like Multics before it, the bet is on multiplexing: a small team shouldn't feel small. With the right system, two engineers and a fleet of agents can move like twenty.

Features

Multica manages the full agent lifecycle: from task assignment to execution monitoring to skill reuse.

  • Agents as Teammates — assign to an agent like you'd assign to a colleague. They have profiles, show up on the board, post comments, create issues, and report blockers proactively.
  • Squads — group agents (and humans) under a leader agent and assign work to the squad. The leader decides who should pick it up, so routing stays stable as the team grows. @FrontendTeam instead of @alice-or-bob-or-carol.
  • Autonomous Execution — set it and forget it. Full task lifecycle management (enqueue, claim, start, complete/fail) with real-time progress streaming via WebSocket.
  • Autopilots — schedule recurring work for agents. Cron triggers, webhooks, or manual runs — each autopilot creates the issue and routes it to an agent automatically, so daily standups, weekly reports, and periodic audits run themselves.
  • Reusable Skills — every solution becomes a reusable skill for the whole team. Deployments, migrations, code reviews — skills compound your team's capabilities over time.
  • Unified Runtimes — one dashboard for all your compute. Local daemons and cloud runtimes, auto-detection of available CLIs, real-time monitoring.
  • Multi-Workspace — organize work across teams with workspace-level isolation. Each workspace has its own agents, issues, and settings.

Quick Install

brew install multica-ai/tap/multica

Use brew upgrade multica-ai/tap/multica to keep the CLI current.

macOS / Linux (install script)

curl -fsSL https://raw.githubusercontent.com/multica-ai/multica/main/scripts/install.sh | bash

Use this if Homebrew is not available. The script installs the Multica CLI on macOS and Linux by using Homebrew when it is on PATH, otherwise it downloads the binary directly.

Windows (PowerShell)

irm https://raw.githubusercontent.com/multica-ai/multica/main/scripts/install.ps1 | iex

Then configure, authenticate, and start the daemon in one command:

multica setup          # Connect to Multica Cloud, log in, start daemon

Self-hosting? Add --with-server to deploy a full Multica server on your machine:

curl -fsSL https://raw.githubusercontent.com/multica-ai/multica/main/scripts/install.sh | bash -s -- --with-server
multica setup self-host

This pulls the official Multica images from GHCR (latest stable by default). Requires Docker. See the Self-Hosting Guide for details. If the selected GHCR tag has not been published yet, fall back to make selfhost-build from a checkout.


Getting Started

1. Set up and start the daemon

multica setup           # Configure, authenticate, and start the daemon

The daemon runs in the background and auto-detects agent CLIs (claude, codex, copilot, openclaw, opencode, hermes, gemini, pi, cursor-agent, kimi, kiro-cli) on your PATH.

2. Verify your runtime

Open your workspace in the Multica web app. Navigate to Settings → Runtimes — you should see your machine listed as an active Runtime.

What is a Runtime? A Runtime is a compute environment that can execute agent tasks. It can be your local machine (via the daemon) or a cloud instance. Each runtime reports which agent CLIs are available, so Multica knows where to route work.

3. Create an agent

Go to Settings → Agents and click New Agent. Pick the runtime you just connected and choose a provider (Claude Code, Codex, GitHub Copilot CLI, OpenClaw, OpenCode, Hermes, Gemini, Pi, Cursor Agent, Kimi, or Kiro CLI). Give your agent a name — this is how it will appear on the board, in comments, and in assignments.

4. Assign your first task

Create an issue from the board (or via multica issue create), then assign it to your new agent. The agent will automatically pick up the task, execute it on your runtime, and report progress — just like a human teammate.


CLI

The multica CLI connects your local machine to Multica — authenticate, manage workspaces, and run the agent daemon.

Command Description
multica login Authenticate (opens browser)
multica daemon start Start the local agent runtime
multica daemon status Check daemon status
multica setup One-command setup for Multica Cloud (configure + login + start daemon)
multica setup self-host Same, but for self-hosted deployments
multica workspace list List your workspaces (current is marked with *)
multica workspace switch <id|slug> Switch the default workspace for this profile
multica issue list List issues in your workspace
multica issue create Create a new issue
multica update Update to the latest version

See the CLI and Daemon Guide for the full command reference.


Architecture

┌──────────────┐     ┌──────────────┐     ┌──────────────────┐
│   Next.js    │────>│  Go Backend  │────>│   PostgreSQL     │
│   Frontend   │<────│  (Chi + WS)  │<────│   (pgvector)     │
└──────────────┘     └──────┬───────┘     └──────────────────┘
                            │
                     ┌──────┴───────┐
                     │ Agent Daemon │  runs on your machine
                     └──────────────┘  (Claude Code, Codex, GitHub Copilot CLI,
                                        OpenCode, OpenClaw, Hermes, Gemini,
                                        Pi, Cursor Agent, Kimi, Kiro CLI)
Layer Stack
Frontend Next.js 16 (App Router)
Backend Go (Chi router, sqlc, gorilla/websocket)
Database PostgreSQL 17 with pgvector
Agent Runtime Local daemon executing Claude Code, Codex, GitHub Copilot CLI, OpenClaw, OpenCode, Hermes, Gemini, Pi, Cursor Agent, Kimi, or Kiro CLI

Development

For contributors working on the Multica codebase, see the Contributing Guide.

Prerequisites: Node.js v20+, pnpm v10.28+, Go v1.26+, Docker

make dev

make dev auto-detects your environment (main checkout or worktree), creates the env file, installs dependencies, sets up the database, runs migrations, and starts all services.

See CONTRIBUTING.md for the full development workflow, worktree support, testing, and troubleshooting.

An iOS mobile client lives in apps/mobile/ — see its README for how to build it onto your own iPhone.

Description
No description provided
Readme 270 MiB
Languages
Go 48.6%
TypeScript 42.8%
MDX 7.1%
PLpgSQL 0.4%
CSS 0.4%
Other 0.6%