Skip to content
Troubleshooting

Troubleshooting

Permission denied writing LaunchAgents plist

When running loop daemon:start, you may see:

Error: writing plist: open /Users/<user>/Library/LaunchAgents/com.loop.agent.plist: permission denied

This happens when ~/Library/LaunchAgents/ or the plist file is owned by root instead of your user. Deleting or writing the plist also requires write permission on the directory itself. Fix both by reclaiming ownership:

sudo chown $(whoami):staff ~/Library/LaunchAgents
sudo chown $(whoami):staff ~/Library/LaunchAgents/com.loop.agent.plist

Then retry loop daemon:start.

Docker build fails with TLS certificate errors

When running loop serve, the Docker image build may fail with:

E: Failed to fetch https://deb.debian.org/debian/dists/.../Release.gpg  Server certificate verification failed. CAfile: ...
E: Some index files failed to download.

This occurs on corporate networks where a proxy or firewall (e.g. Palo Alto, Zscaler, Netskope) performs SSL inspection using a custom CA certificate that Docker containers don’t trust.

Fix: inject corporate CA certificates into the build

  1. Export your corporate CA certificates from the macOS system keychain:

    security find-certificate -a -p /Library/Keychains/System.keychain > ~/.loop/container/corporate-ca.pem
  2. Patch ~/.loop/container/Dockerfile to inject the certificates before any network operations. Add these lines after each FROM line and before any RUN apt-get or RUN curl commands:

    COPY corporate-ca.pem /usr/local/share/ca-certificates/corporate-ca.crt
    RUN update-ca-certificates

    For example, the builder stage becomes:

    FROM golang:1.27 AS builder
    COPY corporate-ca.pem /usr/local/share/ca-certificates/corporate-ca.crt
    RUN update-ca-certificates

    Apply the same pattern to the final stage and to ~/.loop/container/chrome.Dockerfile (note: chrome.Dockerfile is still Alpine — use the cat … >> /etc/ssl/certs/ca-certificates.crt form there).

  3. Retry loop serve.

The agent image sets AWS_CA_BUNDLE=/etc/ssl/certs/ca-certificates.crt, because the AWS CLI checks TLS against its own CA list rather than the system store; with the CA added as above, aws stops failing with CERTIFICATE_VERIFY_FAILED. Set envs.AWS_CA_BUNDLE to point it elsewhere.

Note: When loop serve ships a new version of the embedded container files, your edited ~/.loop/container/Dockerfile (and chrome.Dockerfile) is preserved as Dockerfile.bkp (and chrome.Dockerfile.bkp) before being overwritten. Diff the .bkp against the refreshed file to re-apply your local changes.

Daemon not healthy after daemon:start

The desktop app polls GET /api/health for ~15 seconds after launching the daemon; if it never turns healthy you’ll see a “daemon not running” state.

  1. Check the log — the daemon writes to ~/.loop/loop.log by default (configurable via log_file). Startup failures (bad config, port in use, Docker unreachable) land there with the reason.
  2. Run it in the foreground — loop serve runs the same daemon attached to your terminal, which surfaces config errors immediately. daemon:start is the supervised background variant (LaunchAgents on macOS); loop daemon:status shows whether it’s loaded.
  3. Common causes: another process bound to the API port (api_addr, default :8222), a malformed ~/.loop/config.json (it’s HJSON — comments are fine, but unbalanced braces are not), or Docker not running (the daemon starts without Docker but container features will error).

Security gate: command blocked or approval prompt never resolves

The seccomp gate (gates.agentgate) is enabled by default and runs inside every agent container. Most operations are allowed silently; credential-adjacent writes and container-escape shapes either deny or prompt.

  • A prompt is waiting somewhere you’re not looking. Approval cards render in the chat for chat-initiated work, and as an overlay on the specific terminal pane for terminal-initiated work. The blocked syscall waits until you resolve the card — an agent that seems “stuck” often has a pending approval on another surface. The Audit panel lists recent gate decisions.
  • A legitimate operation keeps getting blocked. Decisions come from first-match-wins rules; the fall-through is gates.agentgate.default_decision ("allow" by default). Add a project-level rule for the specific path/command rather than loosening globals: project rules come before the global approves and allows, so a narrow allow there wins over a broader global one. A deny you wrote in the global config always wins; an exception to it belongs there. Loop’s default credential denies (~/.aws, ~/.kube, ~/.ssh, …) are the exception: a project allow for them works once you trust the project config. If it has no effect, check that the config is trusted and unchanged since (Settings shows a “Project config changed” notice).
  • Kill switch. Set gates.agentgate.enabled: false in the global config. The project setting is ignored, so a project can neither switch the gate off nor back on. Disabling the gate also disables the Docker proxy. Containers created before a config change keep their old policy — recreate them to pick up new rules.

Docker proxy blocks an agent’s Docker command

When the gate is enabled, agents talk to a filtered Docker socket (loop dockerproxy), not the real daemon. Denied calls return 403 inside the container; approve-rule matches block until you resolve the approval card.

  • Check the Audit panel to see which rule matched.
  • Add or adjust gates.docker_proxy.http_rules or body_rules for what’s being blocked. Project rules may use any decision but come after the global denies; gates.docker_proxy.enabled is set globally only.

Messages you may see from docker (or a library) inside the agent, and what to do:

MessageCauseFix
bind source <path>: … (sources are checked before the container is created, so they must exist) (400)The proxy resolves every bind source before the create, so -v ./dir:/x no longer creates a missing dirCreate the directory first (mkdir -p dir)
binds under the agent's mounts need Docker API >= 1.45 or docker socket mounts need Docker API >= 1.45 (400)The client pins an older API (DOCKER_API_VERSION, an old SDK); pinned binds and the nested socket use VolumeOptions.SubpathUnset the pin or raise it to 1.45+
container-escape risk: bind-mount or flag rejected (403)A baseline deny matched: a system-path bind, --privileged, a host namespace, a --cap-add outside the allowlist, a non-runc runtime, …Check the Audit panel for the field; see Gates: Default policy . Add a project body_rules allow only if you accept the risk
Approval card: bind mount outside the agent's own mountsA host path the agent doesn’t mount read-write (or a read-write bind of one it mounts read-only), e.g. /tmp, a sibling projectApprove it, or bind a path inside the workspace instead
ambiguous request body: keys differ only in case (400)The body has e.g. both HostConfig and hostconfigFix the client; the daemon would keep one of them depending on key order
container create body too large (413)Create bodies over 1 MiB can’t be inspectedMove large env/labels into files
volume names starting with loop-bind- are reserved for the docker proxy (403)A request mounts or creates a loop-bind-* volume by nameUse a different volume name
docker socket mounts are unavailable: the nested proxy socket is not configured (403)The proxy couldn’t set up its second socket at startupRecreate the agent container; creates without a socket mount still work

Docker Desktop’s volume list shows loop-bind-<hash> volumes: those back pinned binds of containers the agent started (see Binds are pinned to the agent’s mounts ). Loop removes the unused ones whenever it removes a container; docker volume prune -a --filter label=app=loop-bind removes them by hand (-a, since they are named volumes).

Run fails with “Docker is out of disk space”

The disk that fills is the Docker VM’s (Docker Desktop’s virtual disk), not the host’s, so freeing host space doesn’t help. Container create, file copies and the agent’s own writes fail with no space left on device, and the chat shows the notice instead of a reply. Free space inside Docker, then send the message again:

  • docker builder prune -a – the build cache is usually the largest share.
  • docker image prune -a and docker container prune – unused images and stopped containers.
  • docker system df shows what is using the space.

See Run Failures for how other run errors show.

Agent fails with “No conversation found with session ID”

Claude Code prunes transcripts under ~/.claude/projects after cleanupPeriodDays (30 by default), while Loop pins a channel’s session id until a run succeeds — so a channel idle for longer than the window points at a file that is gone, and every turn fails on --resume.

Loop now detects the missing transcript and starts a fresh session instead (agent runs, terminal panes, and new worktrees alike; see Pruned transcripts ), so this should self-correct. If you hit it on an older build, or you want the conversation back rather than a fresh start:

  • Recover the transcript if a copy survives. Worktrees get their own copy under ~/.claude/projects/<encoded-worktree-path>/, and those copies are pruned on their own schedule — ls -t ~/.claude/projects/*/<session-id>.jsonl often turns one up. Copy it back into the channel’s own project dir (the dir path with / and . replaced by -).
  • Or start clean — clear the channel’s stored session from the Sessions panel (+ New) and let the next turn open a new one.
  • Keep transcripts longer by raising cleanupPeriodDays in ~/.claude/settings.json.

Memory search returns nothing / Ollama issues

Semantic memory is off by default — search_memory silently returns no results until you set memory.enabled: true in the global config and configure memory.paths.

When enabled, the first Embed() call lazily starts a local loop-ollama Docker container (port 11434, model nomic-embed-text, cached in the loop-ollama named volume), and the container auto-stops after ~5 minutes idle. Failure modes:

  • Port conflict — if something else (e.g. a native Ollama install) is already bound to 11434, the container can’t start. Stop the other instance or point memory.embeddings at it instead of the managed container.
  • First-use model pull is slow — the initial nomic-embed-text download happens inside the container on first index/search; on corporate networks it’s subject to the same TLS-inspection issues as the image build above. Subsequent runs use the named volume.
  • Indexing lag — re-indexing runs at startup and then on a ticker (default 5 minutes, memory.reindex_interval_sec); files edited moments ago may not be searchable yet.