stacklok / stacklok/toolhive

AtomicWriteFile fails with EBUSY when target is a bind-mounted file (breaks claude-code client in Flatpak)

Open
#3,979 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

bug
Dominant language
Go
Stars
2.2k
Forks
300
Avg merge
1d 15h
Merged PRs (30d)
184

Description

Bug description

When ToolHive is run inside a Flatpak sandbox with --filesystem=~/.claude.json granted, registering the claude-code client fails with EBUSY. This is caused by AtomicWriteFile (pkg/fileutils/atomic.go) using a write-to-temp-then-rename pattern. When Flatpak grants access to an individual file path, bubblewrap bind-mounts that specific file as a mount point inside the sandbox namespace. rename() onto a mount point returns EBUSY on Linux, so the atomic replacement fails.

claude-code is the only affected client because it is the only one whose config file lives directly in the home directory root (~/.claude.json, RelPath: []string{}). All other clients use subdirectory paths, so Flatpak grants directory-level access - both the temp file and the rename target live inside the same bind-mounted directory, where rename works freely.

Steps to reproduce

  1. Package an app that invokes thv inside a Flatpak sandbox (ToolHive Desktop does this, or you can vibe code a minimal sample)
  2. Grant --filesystem=~/.claude.json in the Flatpak manifest
  3. Ensure ~/.claude.json exists on the host (e.g. Claude Code is installed)
  4. Run thv client register claude-code with at least one MCP server running. Important: it has to be running inside the Flatpak environment

Probably it can be reproduced also with bubblewrap directly, as there should be nothing Flatpak-specific involved. So instead of bundling a whole flatpak package, you can probably just run thv using bubblewrap directly, and create the same mount.

Expected behavior

The claude-code client is registered and ~/.claude.json is updated with the running MCP server entries.

Actual behavior

The write fails with:

rename /home/$USER/.tmp-2168705275 /home/$USER/.claude.json: device or resource busy

Environment (if relevant)

  • OS/version: Linux (any distro running Flatpak with bubblewrap)
  • ToolHive version: any

Additional context

The same issue would affect any other sandboxed environment that bind-mounts individual files, such as Docker with a specific file volume mount (-v /host/file:/container/file).

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with pkg/fileutils/atomic.go and inspect AtomicWriteFile's temporary-file and rename flow. Reproduce the failure with a specific-file bind mount using bubblewrap, then verify the behavior against the expected claude-code registration result. Done means registration updates ~/.claude.json successfully without EBUSY while preserving the existing atomic-write behavior where possible.

Written by the indexing model from the issue text.

Assessment

Tech stack
go, linux
Domain
backend, operating-systems
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Clearly specified
Newbie friendliness
52/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.