AtomicWriteFile fails with EBUSY when target is a bind-mounted file (breaks claude-code client in Flatpak)
Nobody has claimed this yet.
- 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
- Package an app that invokes
thvinside a Flatpak sandbox (ToolHive Desktop does this, or you can vibe code a minimal sample) - Grant
--filesystem=~/.claude.jsonin the Flatpak manifest - Ensure
~/.claude.jsonexists on the host (e.g. Claude Code is installed) - Run
thv client register claude-codewith 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
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- 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