# How to Read Cowork Daemon Logs and Troubleshoot Startup Failures in Claude Desktop

> Troubleshoot Claude Desktop Cowork daemon startup failures by examining the cowork_vm_daemon.log. Learn to grep logs for errors and diagnose initialization crashes or system kills efficiently.

- Repository: [Aaddrick/claude-desktop-debian](https://github.com/aaddrick/claude-desktop-debian)
- Tags: how-to-guide
- Published: 2026-04-19

---

**To diagnose Cowork startup failures in Claude Desktop, examine the daemon lifecycle log at `~/.config/Claude/logs/cowork_vm_daemon.log` and use `grep` to isolate startup, exit, and error events—the missing or final log entry reveals whether the daemon failed to fork, crashed during initialization, or was killed by the system.**

The `aaddrick/claude-desktop-debian` repository packages Claude Desktop for Linux systems, including the **Cowork VM daemon** ([`cowork-vm-service.js`](https://github.com/aaddrick/claude-desktop-debian/blob/main/cowork-vm-service.js)) that hosts the isolated VM for Cowork mode. When the Electron front-end cannot establish a connection, the daemon's own logs provide the definitive diagnostic record. This guide explains how to locate these logs, interpret the lifecycle events, and resolve the most common Linux-specific startup blockers.

## Understanding the Cowork Daemon Architecture

The Cowork daemon follows a strict initialization sequence that is instrumented with lifecycle logging. Understanding this sequence helps you read the logs effectively.

### Auto-Launch Mechanism

The Electron client triggers daemon startup through an injection in [`build.sh`](https://github.com/aaddrick/claude-desktop-debian/blob/main/build.sh) (Patch 6). When the Unix-domain socket at `$XDG_RUNTIME_DIR/cowork-vm-service.sock` is missing, the client calls `child_process.fork()` with `detached:true`, redirecting the child’s `stdio` to the log file. To prevent fork bombs during crash loops, a cooldown guard (`FUNC._lastSpawn`) rate-limits restart attempts.

### Lifecycle Logging Infrastructure

Inside [`scripts/cowork-vm-service.js`](https://github.com/aaddrick/claude-desktop-debian/blob/main/scripts/cowork-vm-service.js), the `logLifecycle()` function writes unconditional entries for critical events: `startup`, `listening`, `SIGTERM`, `SIGINT`, and `uncaughtException`. This ensures that even silent crashes or external kills leave a trace in the log file.

### Socket Creation Sequence

The daemon emits a `lifecycle startup …` line immediately upon invocation. After initializing the VM environment, it creates the Unix socket and writes `lifecycle listening …`. If the log shows `startup` but never reaches `listening`, the daemon crashed during VM initialization—typically due to bubblewrap namespace restrictions or corrupted bundle files.

## Locating and Reading the Daemon Logs

### Primary Log File Location

The daemon writes its lifecycle log to a fixed path under your home directory:

```

~/.config/Claude/logs/cowork_vm_daemon.log

```

This path is defined in the logging implementation within [`scripts/cowork-vm-service.js`](https://github.com/aaddrick/claude-desktop-debian/blob/main/scripts/cowork-vm-service.js) (source line 5). The client-side launcher logs complementary auto-launch events to `~/.cache/claude-desktop-debian/launcher.log`, which shows `[cowork-autolaunch]` messages when the fork is attempted.

### Real-Time Log Monitoring

To watch the daemon startup in real time, stream the log file using `tail`:

```bash
tail -f ~/.config/Claude/logs/cowork_vm_daemon.log

```

Then attempt to start a Cowork session from the Claude Desktop interface. The log will update live as the daemon initializes, listens, or crashes.

### Key Lifecycle Events to Grep For

Isolate the most diagnostic entries by filtering for lifecycle events:

```bash
grep -E 'lifecycle (startup|exit|SIGTERM|SIGINT|uncaughtException|unhandledRejection)' \
    ~/.config/Claude/logs/cowork_vm_daemon.log | tail -20

```

Missing `startup` indicates the client never successfully forked the daemon. `startup` without `listening` indicates a crash during initialization. `uncaughtException` reveals Node-level errors, often from corrupted VM bundles.

## Common Startup Failure Patterns

| Symptom (Client) | Log Pattern | Likely Cause | Fix / Investigation |
|-------------------|-------------|--------------|---------------------|
| **"Starting VM…" hangs, reconnect loop** | No `startup` line, or only `startup` then nothing | Fork failed (`fork()` didn’t complete) – usually a permission or AppArmor issue. Check `launcher.log` for `[cowork-autolaunch]` errors. | Ensure the daemon can create its log directory; verify AppArmor profile permits the fork. |
| **`bwrap is installed but cannot create a user namespace`** | `bwrap is installed but cannot create a user namespace` in `cowork_vm_daemon.log` (documented in [`TROUBLESHOOTING.md`](https://github.com/aaddrick/claude-desktop-debian/blob/main/TROUBLESHOOTING.md)) | Ubuntu 24.04+ ships with `apparmor_restrict_unprivileged_userns=1`, blocking bubblewrap’s `userns`. | Install a permissive AppArmor profile for `/usr/bin/bwrap` (see "Permit user namespaces" block in [`docs/TROUBLESHOOTING.md`](https://github.com/aaddrick/claude-desktop-debian/blob/main/docs/TROUBLESHOOTING.md)). |
| **`SIGKILL` / OOM** | `lifecycle startup …` then a gap, no further entries | The daemon was killed externally (OOM killer, `kill -9`). | Look at `dmesg` or system logs for OOM events; increase memory or limit other processes. |
| **`uncaughtException`** | `lifecycle uncaughtException …` line with stack trace | Node-level bug, often caused by corrupted bundle files. | Remove the offending bundle directory (`~/.config/Claude/vm_bundles/claudevm.bundle/`), then restart. |
| **Preserved image files prevent startup** | No `startup` entry at all after a fresh launch | `sessiondata.img` or `rootfs.img.zst` left from a previous broken install blocks daemon initialization (see "Secondary cause" in daemon learnings). | Delete the bundle directory mentioned above or let the auto-reinstall code clean those files (Patch 6b adds them to the delete list). |

## Step-by-Step Diagnostic Commands

Run these commands in sequence to isolate the root cause of a Cowork startup failure:

```bash

# 1. Verify the daemon process (if any)

pgrep -af cowork-vm-service

# 2. Verify the socket exists

ls -la "${XDG_RUNTIME_DIR:-/tmp}/cowork-vm-service.sock"

# 3. Stream the daemon lifecycle log (real-time)

tail -f ~/.config/Claude/logs/cowork_vm_daemon.log

# 4. Inspect recent key events (startup, exit, signals)

grep -E 'lifecycle (startup|exit|SIGTERM|SIGINT|uncaughtException|unhandledRejection)' \
    ~/.config/Claude/logs/cowork_vm_daemon.log | tail -20

# 5. Look for orphan sockets (helpful if the client hangs)

lsof -U 2>/dev/null | grep -iE 'cowork|claude'

# 6. Force a respawn test – kill the daemon and watch the client reconnect

pkill -9 -f cowork-vm-service.js
tail -f ~/.cache/claude-desktop-debian/launcher.log   # watch for [cowork-autolaunch] messages

# 7. If bubblewrap user-ns is blocked (Ubuntu 24.04+)

sudo tee /etc/apparmor.d/bwrap <<'EOF'
abi <abi/4.0>,
include <tunables/global>

profile bwrap /usr/bin/bwrap flags=(unconfined) {
    userns,
    include if exists <local/bwrap>
}
EOF
sudo apparmor_parser -r /etc/apparmor.d/bwrap

```

All commands are safe, read-only operations except the `sudo` block, which modifies the local system’s AppArmor policy as described in the official troubleshooting documentation.

## Summary

- The Cowork daemon writes a detailed lifecycle log to `~/.config/Claude/logs/cowork_vm_daemon.log`.
- Missing or incomplete log entries pinpoint whether the daemon never started, was killed, or crashed.
- Common Linux-specific roadblocks are bubblewrap user-namespace restrictions (fixed with an AppArmor profile) and stale VM bundle images (fix by deleting `~/.config/Claude/vm_bundles/claudevm.bundle/`).
- Use the diagnostic commands above to inspect the process, socket, and log; then apply the appropriate remediation from the failure patterns table.

Following this workflow lets you quickly locate and resolve most Cowork startup failures without needing to rebuild the whole application.

## Frequently Asked Questions

### Where exactly does the Cowork daemon write its logs?

The daemon writes its lifecycle log to `~/.config/Claude/logs/cowork_vm_daemon.log`. This path is hardcoded in the `logLifecycle()` function within [`scripts/cowork-vm-service.js`](https://github.com/aaddrick/claude-desktop-debian/blob/main/scripts/cowork-vm-service.js). The client-side launcher writes complementary auto-launch messages to `~/.cache/claude-desktop-debian/launcher.log`.

### Why does the log show "startup" but never "listening"?

When you see a `lifecycle startup` entry without a subsequent `lifecycle listening` entry, the daemon initialized but crashed during VM setup before creating the Unix socket. This typically indicates a **bubblewrap user-namespace restriction** on Ubuntu 24.04+ or corrupted VM bundle files in `~/.config/Claude/vm_bundles/claudevm.bundle/`.

### How do I fix the "bwrap cannot create a user namespace" error?

Ubuntu 24.04 and newer restrict unprivileged user namespaces by default. Create a permissive AppArmor profile for bubblewrap by writing the configuration to `/etc/apparmor.d/bwrap` with the `userns` permission flag, then reload the profile with `sudo apparmor_parser -r /etc/apparmor.d/bwrap`. This allows the daemon to create the isolated environment required for Cowork mode.

### What should I delete if the daemon keeps crashing on startup?

If the log shows `uncaughtException` or the daemon fails to start entirely, delete the VM bundle directory at `~/.config/Claude/vm_bundles/claudevm.bundle/`. This directory contains `sessiondata.img` and `rootfs.img.zst` files that may be corrupted or locked from a previous failed session. The daemon will re-extract these files on the next launch.