{"id":44358805,"url":"https://github.com/patrickjaja/claude-cowork-service","last_synced_at":"2026-04-09T00:09:56.017Z","repository":{"id":337841760,"uuid":"1155226448","full_name":"patrickjaja/claude-cowork-service","owner":"patrickjaja","description":null,"archived":false,"fork":false,"pushed_at":"2026-04-03T01:12:33.000Z","size":118082,"stargazers_count":75,"open_issues_count":2,"forks_count":6,"subscribers_count":2,"default_branch":"main","last_synced_at":"2026-04-03T03:57:44.831Z","etag":null,"topics":["anthropic","arch-linux","aur","claude","claude-desktop","cowork","deb","go","golang","linux","rpm"],"latest_commit_sha":null,"homepage":null,"language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/patrickjaja.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-02-11T09:15:56.000Z","updated_at":"2026-04-02T11:09:36.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/patrickjaja/claude-cowork-service","commit_stats":null,"previous_names":["patrickjaja/claude-cowork-service"],"tags_count":51,"template":false,"template_full_name":null,"purl":"pkg:github/patrickjaja/claude-cowork-service","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/patrickjaja%2Fclaude-cowork-service","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/patrickjaja%2Fclaude-cowork-service/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/patrickjaja%2Fclaude-cowork-service/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/patrickjaja%2Fclaude-cowork-service/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/patrickjaja","download_url":"https://codeload.github.com/patrickjaja/claude-cowork-service/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/patrickjaja%2Fclaude-cowork-service/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31579093,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-08T14:31:17.711Z","status":"ssl_error","status_checked_at":"2026-04-08T14:31:17.202Z","response_time":54,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["anthropic","arch-linux","aur","claude","claude-desktop","cowork","deb","go","golang","linux","rpm"],"created_at":"2026-02-11T17:07:07.547Z","updated_at":"2026-04-09T00:09:55.989Z","avatar_url":"https://github.com/patrickjaja.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# claude-cowork-service\n\n[![Claude Desktop](https://img.shields.io/endpoint?url=https://patrickjaja.github.io/claude-cowork-service/badges/version-check.json)](https://claude.ai/download)\n[![AUR version](https://img.shields.io/aur/version/claude-cowork-service)](https://aur.archlinux.org/packages/claude-cowork-service)\n[![APT repo](https://img.shields.io/endpoint?url=https://patrickjaja.github.io/claude-cowork-service/badges/apt-repo.json)](https://patrickjaja.github.io/claude-cowork-service/)\n[![RPM repo](https://img.shields.io/endpoint?url=https://patrickjaja.github.io/claude-cowork-service/badges/rpm-repo.json)](https://patrickjaja.github.io/claude-cowork-service/)\n[![Nix flake](https://img.shields.io/endpoint?url=https://patrickjaja.github.io/claude-cowork-service/badges/nix.json)](https://github.com/patrickjaja/claude-cowork-service/blob/main/flake.nix)\n[![Build \u0026 Release](https://github.com/patrickjaja/claude-cowork-service/actions/workflows/build-and-release.yml/badge.svg)](https://github.com/patrickjaja/claude-cowork-service/actions/workflows/build-and-release.yml)\n\nNative Linux backend for Claude Desktop's **Cowork** feature. Reverse-engineered from Windows [`cowork-svc.exe`](https://github.com/anthropics/cowork-win32-service) bundled with Claude Desktop v1.1.4173.\n\n## What This Is\n\nClaude Desktop has a Cowork feature that lets you delegate tasks to a sandboxed Claude Code instance. On macOS it uses Apple's Virtualization framework (Swift), on Windows it uses Hyper-V (Go). On Linux — there's no official support.\n\nThis daemon fills that gap. It implements the same length-prefixed JSON-over-Unix-socket protocol that Claude Desktop expects, but instead of managing a VM, it runs commands directly on the host.\n\n**Key insight:** The VM on macOS/Windows runs Linux anyway. On Linux, we skip the VM and execute natively — because we're already the target OS.\n\n## Installation\n\n### Debian / Ubuntu (APT Repository)\n\n```bash\n# Add repository (one-time setup)\ncurl -fsSL https://patrickjaja.github.io/claude-cowork-service/install.sh | sudo bash\n\n# Install\nsudo apt install claude-cowork-service\n```\n\nUpdates are automatic via `sudo apt update \u0026\u0026 sudo apt upgrade`.\n\n### Fedora / RHEL (DNF Repository)\n\n```bash\n# Add repository (one-time setup)\ncurl -fsSL https://patrickjaja.github.io/claude-cowork-service/install-rpm.sh | sudo bash\n\n# Install\nsudo dnf install claude-cowork-service\n```\n\nUpdates are automatic via `sudo dnf upgrade`.\n\n\u003cdetails\u003e\n\u003csummary\u003eManual .rpm install (without DNF repo)\u003c/summary\u003e\n\n```bash\nsudo dnf install https://github.com/patrickjaja/claude-cowork-service/releases/latest/download/claude-cowork-service-latest.x86_64.rpm\n```\n\u003c/details\u003e\n\n### Arch Linux (AUR)\n\n```bash\nyay -S claude-cowork-service\n```\n\nUpdates arrive through your AUR helper (e.g. `yay -Syu`).\n\n### NixOS\n\n```nix\n# In your flake.nix:\n{\n  inputs.claude-cowork-service.url = \"github:patrickjaja/claude-cowork-service\";\n\n  # In your NixOS configuration:\n  imports = [ inputs.claude-cowork-service.nixosModules.default ];\n  services.claude-cowork.enable = true;\n}\n```\n\n\u003e **Note:** The cowork service invokes `claude` internally, which must be in the\n\u003e systemd service PATH. Systemd user services don't inherit your shell's PATH,\n\u003e so you need to declare it explicitly via `extraPath`:\n\u003e\n\u003e ```nix\n\u003e # Claude Code installed via npm global:\n\u003e services.claude-cowork.extraPath = [ pkgs.nodejs \"/path/to/directory/containing/claude\" ];\n\u003e\n\u003e # Claude Code available as a Nix package:\n\u003e services.claude-cowork.extraPath = [ pkgs.claude-code ];\n\u003e ```\n\u003e\n\u003e **Dispatch** requires Claude Code \u003e= 2.1.86 (fixes `CLAUDE_CODE_BRIEF` env parsing). If nixpkgs ships an older version, [install Claude Code manually](https://docs.anthropic.com/en/docs/claude-code/overview) and use `extraPath` to point to it.\n\nOr run directly with Nix:\n```bash\nnix run github:patrickjaja/claude-cowork-service\n```\n\n\u003e **Note:** Update by running `nix flake update` to pull the latest version. `nix run` always fetches the latest.\n\n### ARM64 / aarch64 (NVIDIA DGX Spark, Jetson, etc.)\n\nARM64 packages are available via the same APT and DNF repos — your package manager picks the correct architecture automatically. The quick install script also auto-detects ARM64.\n\n### Quick Install (Any Distro, x86_64 + ARM64)\n\n```bash\ncurl -fsSL https://raw.githubusercontent.com/patrickjaja/claude-cowork-service/main/scripts/install.sh | bash\n```\n\nThis auto-detects your architecture (x86_64 or aarch64), downloads the correct binary, installs it to `/usr/local/bin/`, creates a systemd user service, and starts it.\n\n\u003e **Note:** This method does not receive automatic updates. Re-run the command to update manually.\n\nTo install without root (uses `~/.local/bin/` instead):\n```bash\ncurl -fsSL https://raw.githubusercontent.com/patrickjaja/claude-cowork-service/main/scripts/install.sh | bash -s -- --user\n```\n\nTo uninstall:\n```bash\ncurl -fsSL https://raw.githubusercontent.com/patrickjaja/claude-cowork-service/main/scripts/install.sh | bash -s -- --uninstall\n```\n\n### From Source\n\nRequires **Go 1.21+** to build.\n\n```bash\ngit clone https://github.com/patrickjaja/claude-cowork-service.git\ncd claude-cowork-service\nmake\nsudo make install                  # installs to /usr/bin (default)\n# or: sudo make PREFIX=/usr/local install  # installs to /usr/local/bin\n```\n\n\u003e **Note:** No automatic updates. Pull and rebuild to update: `git pull \u0026\u0026 make \u0026\u0026 sudo make install`.\n\n## Dependencies\n\n| Category | Dependency | Notes |\n|----------|-----------|-------|\n| **Runtime** | systemd | User service management (`systemctl --user`) |\n| **Runtime** | bash | Binary resolution in launcher scripts |\n| **Required** | Claude Code CLI | `claude` binary must be in `$PATH` — `npm i -g @anthropic-ai/claude-code` recommended (always latest); declared as `optdepends` in packaging so you control the version |\n| **Optional** | socat | Socket health check fallback |\n| **Build (from source)** | Go 1.21+ | The daemon is pure Go with no external library dependencies |\n\n## Claude Code Dependency\n\n**Claude Code is required** for Cowork and Dispatch to function. The daemon spawns `claude` CLI instances for every coding task, agent session, and dispatch interaction.\n\nThe packaging intentionally declares Claude Code as an **optional dependency** (`optdepends`) rather than a hard dependency. This is because Cowork/Dispatch often requires the **latest** Claude Code release -- if it were a hard `depends`, the package manager would install whatever (potentially outdated) version it has cached, which can cause subtle failures. By keeping it optional, you choose your install method and control the version.\n\n**Recommended install** (always gets the latest version):\n\n```bash\nnpm i -g @anthropic-ai/claude-code\n```\n\nAlternative methods:\n\n- **AUR:** `yay -S claude-code` (version may lag behind npm)\n- **Nix:** `nix-env -iA nixpkgs.claude-code` (version may lag behind npm)\n\nAs long as the `claude` binary is on your `$PATH`, the daemon will find it.\n\nFeatures that **require** Claude Code:\n- Delegated coding tasks (Claude Desktop spawns `claude` via `spawn` RPC)\n- Any Cowork session that executes CLI commands through the agent\n- All Dispatch sessions (orchestrator and child)\n\nFeatures that **do not** require Claude Code:\n- The daemon itself (socket protocol, session management, event streaming)\n- Mount path handling and file operations within sessions\n\n## Systemd User Service\n\nThe daemon runs as a systemd user service (`claude-cowork.service`). A key detail: **systemd user services do not inherit your desktop session's environment variables by default.** Without the display-related variables, spawned Claude Code processes cannot access the Wayland/X11 display, clipboard, or D-Bus services.\n\nThe service file solves this with an `ExecStartPre` that imports the necessary variables from the user session:\n\n```ini\nExecStartPre=-/bin/bash -c 'systemctl --user import-environment WAYLAND_DISPLAY XDG_SESSION_TYPE XDG_CURRENT_DESKTOP DISPLAY DBUS_SESSION_BUS_ADDRESS HYPRLAND_INSTANCE_SIGNATURE SWAYSOCK YDOTOOL_SOCKET 2\u003e/dev/null'\n```\n\n| Variable | Purpose |\n|----------|---------|\n| `WAYLAND_DISPLAY` | Wayland compositor socket |\n| `XDG_SESSION_TYPE` | Session type detection (`wayland` / `x11`) |\n| `XDG_CURRENT_DESKTOP` | Desktop environment detection (KDE, GNOME, etc.) |\n| `DISPLAY` | X11/XWayland display |\n| `DBUS_SESSION_BUS_ADDRESS` | D-Bus session bus (clipboard, notifications) |\n| `HYPRLAND_INSTANCE_SIGNATURE` | Hyprland-specific IPC |\n| `SWAYSOCK` | Sway-specific IPC |\n| `YDOTOOL_SOCKET` | ydotool daemon socket (for Computer Use input simulation) |\n\nThe leading `-` on `ExecStartPre` means the service still starts even if the import command fails (e.g. some variables may not exist on all setups).\n\n## Quick Start\n\n```bash\n# 1. Enable and start the daemon\nsystemctl --user enable --now claude-cowork\n\n# 2. Install Claude Desktop (if not already)\nyay -S claude-desktop-bin\n\n# 3. Open Claude Desktop → Cowork tab → send a message\n```\n\n### Verify it's running\n\n```bash\nsystemctl --user status claude-cowork\n```\n\n### Debug mode\n\n```bash\n# Stop systemd service and run manually with debug output\nsystemctl --user stop claude-cowork\ncowork-svc-linux -debug\n```\n\n## How It Works\n\nThe daemon listens on `$XDG_RUNTIME_DIR/cowork-vm-service.sock` and handles 18 RPC methods:\n\n| Method | What it does |\n|--------|-------------|\n| `configure` | Accepts VM config (ignored — no VM) |\n| `createVM` | Creates session directory |\n| `startVM` | Emits `vmStarted` + `apiReachability` events |\n| `stopVM` | Kills all spawned processes, cleans up |\n| `isRunning` | Returns `true` after startVM |\n| `isGuestConnected` | Returns `true` after startVM |\n| `spawn` | Runs command via `os/exec` on host |\n| `kill` | Kills a spawned process (supports signal: SIGTERM, SIGKILL, etc.) |\n| `writeStdin` | Writes data to a process's stdin |\n| `isProcessRunning` | Checks if a process is alive |\n| `mountPath` | Creates symlink (no real mount needed) |\n| `readFile` | Reads file from session directory |\n| `installSdk` | No-op (SDK already on host) |\n| `addApprovedOauthToken` | Stores OAuth token for spawned processes |\n| `setDebugLogging` | Toggles verbose logging |\n| `isDebugLoggingEnabled` | Returns current debug logging state |\n| `subscribeEvents` | Streams process stdout/stderr/exit/startupStep events |\n| `getDownloadStatus` | Returns `\"ready\"` (no bundle needed) |\n\n### What happens during a Cowork session\n\n1. Claude Desktop calls `stopVM` (cleanup), `subscribeEvents`, `startVM`\n2. Daemon emits `vmStarted` and `apiReachability` events\n3. Claude Desktop calls `spawn` with `/usr/local/bin/claude` and OAuth credentials\n4. Daemon remaps the path, resolves the binary, starts `claude` via `os/exec`\n5. Claude Desktop sends `writeStdin` with an `initialize` control request (including `sdkMcpServers`), then user messages\n6. Claude Code's `stream-json` output (on stderr) is emitted as stdout events back to Claude Desktop\n7. SDK MCP tool calls use `control_request`/`control_response` messages embedded in the stdout/stdin streams — Desktop's session manager handles the bidirectional proxy automatically (identical to VM mode)\n8. The UI shows the streamed response in real-time\n\n### Path remapping\n\nClaude Desktop assumes a VM with paths like `/sessions/\u003cname\u003e/mnt/...`. The daemon remaps these to `~/.local/share/claude-cowork/sessions/\u003cname\u003e/` with symlinks for mount points.\n\n## Relationship to claude-desktop-bin\n\nThis package is an **optional companion** to [claude-desktop-bin](https://github.com/patrickjaja/claude-desktop-bin) (the AUR package for Claude Desktop on Linux).\n\n- **Without this daemon:** Claude Desktop works fine for chat. The Cowork tab shows a helpful error message explaining how to install this package.\n- **With this daemon:** Cowork sessions work end-to-end.\n\nThe JS patches in claude-desktop-bin that enable Cowork on Linux are:\n- `fix_cowork_linux.py` — extends TypeScript VM client to Linux, replaces Windows pipe with Unix socket\n- `fix_cowork_error_message.py` — shows Linux-specific guidance when daemon isn't running\n\n## Architecture\n\n```\n┌─────────────────────────────┐\n│ Claude Desktop (Electron)   │\n│  patched with               │\n│  claude-desktop-bin          │\n└──────────┬──────────────────┘\n           │ Length-prefixed JSON\n           │ Unix socket\n┌──────────▼──────────────────┐\n│ cowork-svc-linux (this)     │\n│  native.Backend             │\n│  └─ os/exec on host         │\n└─────────────────────────────┘\n```\n\nCompare to Windows/macOS:\n```\nClaude Desktop → cowork-svc.exe → Hyper-V VM → sdk-daemon (vsock)\nClaude Desktop → cowork-svc     → Apple VM   → sdk-daemon (vsock)\nClaude Desktop → cowork-svc-linux → direct host execution (no VM)\n```\n\n## Protocol Discoveries\n\nDuring reverse engineering, we found 12 mismatches between the documented/expected protocol and what Claude Desktop actually sends. These are documented here for anyone building compatible implementations:\n\n| # | Discovery | Symptom | Fix |\n|---|-----------|---------|-----|\n| 1 | Spawn field is `\"command\"` not `\"cmd\"` | Empty command, process killed immediately | Fixed JSON tag |\n| 2 | Process ID field is `\"id\"` not `\"processId\"` in RPC params | writeStdin data never reached process | Fixed JSON tags |\n| 3 | Binary path `/usr/local/bin/claude` doesn't exist on host | exec.Command failed | Fallback to `exec.LookPath` |\n| 4 | `/sessions/\u003cname\u003e` requires root to create | mkdir failed | Remap to `~/.local/share/claude-cowork/sessions/` |\n| 5 | `subscribeEvents` races with `startVM` | Startup events lost, client stuck | Delay event emission by 500ms |\n| 6 | Client needs `apiReachability` event (not just `isGuestConnected`) | Client stuck after boot | Emit `apiReachability` during startVM |\n| 7 | Args also contain VM paths (not just cwd/env) | `--plugin-dir /sessions/...` unresolvable | Remap args too |\n| 8 | Empty env vars (`ANTHROPIC_API_KEY=\"\"`) break auth | Valid OAuth token ignored | Strip empty env vars |\n| 9 | `sdkMcpServers` in MCP config blocks Claude Code | Process hangs at init — zero output | ~~Strip SDK servers from config~~ **RESOLVED**: was caused by other bugs (#3, #4, #8); SDK servers now pass through and work via event-stream MCP proxy |\n| 10 | Claude Code outputs stream-json on stderr, not stdout | Captured stdout was empty | Emit stderr as stdout events |\n| 11 | MCP proxy requests block Claude Code | Process hangs mid-conversation | ~~Auto-respond with error~~ **RESOLVED**: Desktop's session manager handles `control_request`/`control_response` over the event stream natively |\n| 12 | Event field is `\"id\"` not `\"processId\"` | Events ignored, UI stuck on \"Starting up...\" | Fixed event JSON tags |\n\n## Dispatch Support\n\nDispatch lets you send tasks from the Claude mobile app to your Linux desktop. The cowork-service handles spawning and managing all dispatch sessions natively.\n\n### Architecture: The Ditto Orchestrator\n\nClaude Desktop spawns a long-running **dispatch orchestrator agent** (Anthropic internally calls it \"Ditto\", visible in session directories as `local_ditto_*`). This agent receives messages from the phone, delegates work to child sessions, and sends responses back via the `SendUserMessage` tool.\n\n```\nPhone → Anthropic API → SSE → Claude Desktop → wakes Ditto agent\n  │\n  ├── cowork-service spawns/resumes Ditto CLI session\n  │     Ditto has: SendUserMessage, dispatch MCP tools, all SDK MCP servers\n  │\n  ├── Ditto calls SendUserMessage({message: \"...\"}) → response appears on phone\n  │\n  ├── Ditto calls mcp__dispatch__start_task → Desktop asks cowork-service to spawn child\n  │     └── Child session does the work (code, files, research, etc.)\n  │     └── Child completes → Ditto reads transcript → Ditto replies to phone\n  │\n  └── Ditto can also use: Gmail, Drive, Chrome, Computer Use, scheduled tasks, etc.\n```\n\n### Session types\n\nDesktop uses three session types, identified by the `CLAUDE_CODE_TAGS` environment variable:\n\n| Type | `CLAUDE_CODE_TAGS` | `CLAUDE_CODE_BRIEF` | SendUserMessage | dispatch MCP |\n|------|-------------------|---------------------|-----------------|--------------|\n| Regular cowork | `lam_session_type:chat` | *(not set)* | No | No |\n| Ditto orchestrator | `lam_session_type:agent` | `1` | **Yes** | **Yes** |\n| Dispatch child | `lam_session_type:dispatch_child` | *(not set)* | No | No |\n\n### Linux-specific adaptations\n\nThese adaptations are applied in `native/backend.go` and `native/process.go`:\n\n**1. Strip `--disallowedTools`**\n\nDesktop passes `--disallowedTools` containing tools that the VM runtime handles:\n`AskUserQuestion`, `mcp__cowork__allow_cowork_file_delete`, `mcp__cowork__present_files`,\n`mcp__cowork__launch_code_session`, `mcp__cowork__create_artifact`, `mcp__cowork__update_artifact`.\n\nOn native Linux there is no VM runtime, so we strip the entire flag — all tools are available to the CLI directly.\n\n**2. Inject `--brief` flag (conditional)**\n\nDesktop passes `CLAUDE_CODE_BRIEF=1` in the environment for Ditto/dispatch agent sessions only (not for regular cowork). The backend detects this and injects the `--brief` CLI flag, which ensures the CLI registers `SendUserMessage` in its tool list. This was broken in CLI v2.1.79–2.1.85, fixed in v2.1.86.\n\n**3. Intercept `present_files` locally**\n\nDesktop's built-in `present_files` MCP handler validates file paths against VM-style mounts and rejects native Linux paths (\"not accessible on user's computer\"). The backend intercepts `present_files` control_requests in `streamOutput`, verifies the files exist on disk, and returns a synthetic success response directly to the CLI's stdin — bypassing Desktop entirely.\n\nThe response includes a hint for the model to use `SendUserMessage` with `attachments` for phone delivery, since `present_files` UI cards only appear in the Desktop app.\n\n**4. Reverse mount path mapping**\n\nThe backend builds reverse mount remappings (real host path → VM-style `/sessions/\u003cname\u003e/mnt/\u003cmount\u003e`) applied to outgoing MCP control_requests. This ensures tools other than `present_files` that flow through Desktop's MCP proxy can resolve paths correctly.\n\n### SendUserMessage tool reference\n\nThe key tool for dispatch — how the model sends responses to the phone.\n\n| Parameter | Type | Required | Description |\n|-----------|------|----------|-------------|\n| `message` | string | Yes | Markdown-formatted message content |\n| `attachments` | array | No | File paths (absolute or cwd-relative) for images, diffs, logs |\n| `status` | string | No | `\"normal\"` (replying to user) or `\"proactive\"` (agent-initiated) |\n\n**Note:** `mcp__dispatch__send_message` is a *different* tool — it sends messages between sessions (inter-agent), not to the user's phone. See [SEND_USER_MESSAGE_STATUS.md](https://github.com/patrickjaja/claude-desktop-bin/blob/master/SEND_USER_MESSAGE_STATUS.md) in claude-desktop-bin for the full investigation.\n\n### Debugging dispatch\n\n```bash\n# Run with debug logging to see all spawn args and MCP proxy flow\nsystemctl --user stop claude-cowork\ncowork-svc-linux -debug 2\u003e\u00261 | tee /tmp/cowork-debug.log\n\n# Check what Desktop passes for each session type\ngrep 'DISPATCH-DEBUG' /tmp/cowork-debug.log\n\n# Check if present_files interception fires\ngrep 'present_files handled' /tmp/cowork-debug.log\n\n# Check disallowedTools stripping\ngrep 'stripping --disallowedTools' /tmp/cowork-debug.log\n```\n\n## VM Backend (Dormant)\n\nThe `vm/` directory contains a full QEMU/KVM backend implementation:\n- `vm/manager.go` — VM lifecycle (create, start, stop)\n- `vm/qemu.go` — QEMU instance with direct kernel boot, COW overlays\n- `vm/vsock.go` — AF_VSOCK communication with guest sdk-daemon\n- `vm/bundle.go` — VHDX→qcow2 conversion, zstd decompression\n- `vm/network.go` — QEMU user-mode and bridge networking\n\nThis code works but is not used by the native backend. It's retained for potential future sandboxed execution mode.\n\n## Testing\n\n```bash\n# Build and run with debug logging\nmake \u0026\u0026 ./cowork-svc-linux -debug\n\n# In another terminal, test the protocol\npython3 -c \"\nimport socket, struct, json\nmsg = json.dumps({'method': 'isRunning', 'id': 1}).encode()\nsock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)\nsock.connect('$(echo \\$XDG_RUNTIME_DIR)/cowork-vm-service.sock')\nsock.sendall(struct.pack('\u003eI', len(msg)) + msg)\nlength = struct.unpack('\u003eI', sock.recv(4))[0]\nprint(json.loads(sock.recv(length)))\n\"\n```\n\n## Troubleshooting\n\n### Wayland / Computer Use issues\n\n**Claude Code can't access the display in cowork sessions:**\n\nThe systemd environment may be missing display variables. Check what's imported:\n\n```bash\nsystemctl --user show-environment | grep WAYLAND\nsystemctl --user show-environment | grep DISPLAY\n```\n\nIf the variables are missing, import them manually and restart the service:\n\n```bash\nsystemctl --user import-environment WAYLAND_DISPLAY XDG_SESSION_TYPE DISPLAY DBUS_SESSION_BUS_ADDRESS\nsystemctl --user restart claude-cowork\n```\n\n**ydotool doesn't work in cowork sessions (Computer Use):**\n\nCheck that the `YDOTOOL_SOCKET` variable is in the systemd environment:\n\n```bash\nsystemctl --user show-environment | grep YDOTOOL\n```\n\nIf missing, ensure the ydotool daemon is running and import the socket path:\n\n```bash\nsystemctl --user import-environment YDOTOOL_SOCKET\nsystemctl --user restart claude-cowork\n```\n\n**`claude` binary isn't found:**\n\nThe daemon resolves `claude` from `$PATH` via `bash -lc`. Verify it's accessible:\n\n```bash\nbash -lc \"which claude\"\n```\n\nIf this prints nothing, Claude Code is either not installed or not in your login shell's `$PATH`. See the [Claude Code Dependency](#claude-code-dependency) section for install options.\n\n## Upstream Reference Docs\n\nDeep analysis of the upstream Windows binaries and VM bundle we reverse-engineer against:\n\n| Document | What it tracks |\n|----------|---------------|\n| [COWORK_RPC_PROTOCOL.md](COWORK_RPC_PROTOCOL.md) | All 18 RPC methods, event types, protocol discoveries, Linux adaptations |\n| [COWORK_SVC_BINARY.md](COWORK_SVC_BINARY.md) | `cowork-svc.exe` Go internals, handler functions, app.asar SDK versions, checksums |\n| [COWORK_VM_BUNDLE.md](COWORK_VM_BUNDLE.md) | VM rootfs contents — sdk-daemon, Node.js, Python packages, system packages, checksums |\n\nThese are re-validated on every upstream Claude Desktop release. See [update-prompt.md](update-prompt.md) for the update workflow.\n\n## See Also\n\n- [tweakcc](https://github.com/Piebald-AI/tweakcc) — A great CLI tool for customizing Claude Code (system prompts, themes, UI). Same patching-JS-to-make-it-yours energy. Thanks to the Piebald team for their work.\n\n## Legal Notice\n\n\u003e This is an **unofficial community project** for educational and research purposes.\n\u003e Claude Desktop's Cowork feature is proprietary software owned by **Anthropic PBC**.\n\u003e\n\u003e This repository contains only a reverse-engineered service daemon — not the Claude\n\u003e Desktop application itself. A valid Claude account is required to use Cowork.\n\u003e\n\u003e This project is not affiliated with, endorsed by, or sponsored by Anthropic.\n\u003e \"Claude\" is a trademark of Anthropic PBC.\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpatrickjaja%2Fclaude-cowork-service","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpatrickjaja%2Fclaude-cowork-service","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpatrickjaja%2Fclaude-cowork-service/lists"}