Files
stack/packages/mosaic
fredandClaude Opus 5 3b4055017e
ci/woodpecker/pr/ci Pipeline was canceled
fix(installer): pin umask and set the 0700 modes the fleet boundary requires (#1236)
A greenfield install cannot run `mosaic fleet init --write`. It fails with
`unsafe-permissions` on an unnamed `(directory)` and an unhandled Node throw,
and every mutating `mosaic fleet` command fails the same way. Measured on a
reverted-to-greenfield sandbox VM at CLI 0.0.50-next.2413: `~/.config/mosaic`,
`fleet/` and `credentials/` all land at 0775, and 1735 directories under the
framework root carry `mode & 022`.

Two independent causes, and fixing either one alone leaves it broken.

1. The installer inherited the caller's umask. Debian/Ubuntu ship 002, so every
   `mkdir -p` produced 0775. Fedora/RHEL ship 022 and produced 0755. The
   product therefore worked or did not depending on the operator's login shell,
   with nothing in the install output distinguishing the two. 022 is already
   what this script assumes it produces — `make_durable_snapshot` restores the
   ambient umask specifically so "every later sync copy and new framework dir"
   gets 0644/0755 — so pin it rather than inherit it.

2. Even at a correct 0755, three directories are rejected. The fleet code
   guards its managed paths with two masks in two languages:
   `assertPrivateManagedDirectory` (fleet-reconciler.js, `mode & 0o077`) covers
   MOSAIC_HOME and `fleet/` and runs before the roster lock is taken;
   `assert_private_directory` (tools/fleet/start-agent-session.sh, `mode & 077`)
   covers `fleet/agents` and runs before a pane is spawned. Their laxer
   siblings use `mode & 0o022` and accept 0755. The strict mask wins, so the
   installer states 0700 outright instead of hoping a umask implies it.

The `find -perm /022 -exec chmod go-w` sweep repairs a tree installed before
this change, which the umask alone cannot reach. It strips group/other WRITE
only — never read or execute — and is scoped to directories, so it corrects the
boundary violation without changing who may traverse or read anything. It is
not sufficient for `fleet/agents`: stripping write from 0755 yields 0750 and
`mode & 077` is still non-zero, which is why that path gets its own chmod.

Reported as #1236. The `fleet/agents` half was found by scooby reading
start-agent-session.sh; the umask framing is theirs too — my first report
blamed the distro rather than the umask.

Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01WYgWocp36goy8hj2ui6ps1
2026-08-15 22:29:48 -05:00
..

@mosaicstack/mosaic

CLI package for the Mosaic self-hosted AI agent platform.

Usage

mosaic wizard           # First-run setup wizard
mosaic gateway install  # Install the gateway daemon
mosaic config show      # View current configuration
mosaic config hooks list  # Manage Claude hooks

Headless / CI Installation

Set MOSAIC_ASSUME_YES=1 (or ensure stdin is not a TTY) to skip all interactive prompts. The following environment variables control the install:

Gateway configuration (mosaic gateway install)

Variable Default Required
MOSAIC_STORAGE_TIER local No
MOSAIC_GATEWAY_PORT 14242 No
MOSAIC_DATABASE_URL (none) Yes if tier=team
MOSAIC_VALKEY_URL (none) Yes if tier=team
MOSAIC_ANTHROPIC_API_KEY (none) No
MOSAIC_CORS_ORIGIN http://localhost:3000 No

Admin user bootstrap

Variable Default Required
MOSAIC_ADMIN_NAME (none) Yes (headless)
MOSAIC_ADMIN_EMAIL (none) Yes (headless)
MOSAIC_ADMIN_PASSWORD (none) Yes (headless)

MOSAIC_ADMIN_PASSWORD must be at least 8 characters. In headless mode a missing or too-short password causes a non-zero exit.

Example: Docker / CI install

export MOSAIC_ASSUME_YES=1
export MOSAIC_ADMIN_NAME="Admin"
export MOSAIC_ADMIN_EMAIL="[email protected]"
export MOSAIC_ADMIN_PASSWORD="securepass123"

mosaic gateway install

Runtime launchers

mosaic claude            # Launch Claude Code with Mosaic injection
mosaic yolo claude       # …with --dangerously-skip-permissions
mosaic codex | opencode | pi

mosaic claudex (EXPERIMENTAL)

Runs GPT models inside the Claude Code harness by pointing Claude Code at a local claude-code-proxy that translates the Anthropic Messages API to a ChatGPT-subscription (Codex OAuth) backend. This is not Anthropic Claude — model behavior, tool use, and output quality may differ. Intended for evaluation, not production delivery.

mosaic claudex           # launch (prompts through the proxy readiness gate)
mosaic yolo claudex      # …with --dangerously-skip-permissions
mosaic claudex --print "hello"   # trailing args are forwarded to Claude Code

Prerequisite: the claude-code-proxy binary must be installed and authenticated (claude-code-proxy codex auth …). mosaic claudex runs a preflight that verifies the binary, the OAuth state (triggering a device re-auth if needed), and a trusted local listener before launching; it fails closed if the proxy cannot be brought up with a verified identity.

Isolation (never touches your real Claude state). claudex always launches against an isolated CLAUDE_CONFIG_DIR (default ~/.config/mosaic/claudex/home). The ambient CLAUDE_CONFIG_DIR is deliberately ignored, and a guard proves the resolved dir can never be — or live under — the real ~/.claude. A claudex session therefore cannot mutate your normal Claude Code config.

No token leakage. claudex never reads the proxy's credential file. Claude Code is handed only ANTHROPIC_AUTH_TOKEN=unused pointed at the loopback proxy; the entire credential-bearing env family (ANTHROPIC_*, AWS_*, GOOGLE_CLOUD_*, GOOGLE_APPLICATION_CREDENTIALS, *_TOKEN, *_KEY, *_SECRET, …) is stripped from the composed environment. The Bedrock/Vertex routing switches (CLAUDE_CODE_USE_BEDROCK, CLAUDE_CODE_USE_VERTEX, and the _SKIP_*_AUTH pair) are force-removed regardless of value — otherwise their mere presence would route Claude Code to the real Anthropic API via AWS/GCP and bypass the proxy. The proxy holds the real OAuth credential.

Model tiers (override via env).

Tier Env var Default
primary (opus/sonnet) ANTHROPIC_MODEL gpt-5.6-sol
small/fast (haiku) ANTHROPIC_SMALL_FAST_MODEL gpt-5.6-luna

Operator-provided values win over the defaults. Additional overrides: MOSAIC_CLAUDEX_CONFIG_DIR (isolated config dir), ANTHROPIC_BASE_URL (proxy endpoint).

Hooks management

After running mosaic wizard, Claude hooks are installed in ~/.claude/hooks-config.json.

mosaic config hooks list              # Show all hooks and enabled/disabled status
mosaic config hooks disable PostToolUse  # Disable a hook (reversible)
mosaic config hooks enable PostToolUse   # Re-enable a disabled hook

Set CLAUDE_HOME to override the default ~/.claude directory.