Found on mih09 with Werkator 1.37.0 (repository werkrevisor, PR#5): a CI build and a pull-request review started together and both imported the same new rootfs.
Imports of one image wait for each other
Store.Import stages in <image>.tmp and removes that directory first, so the next import cleans up after an interrupted one.
A second import at the same time removed the first one's files: one failed with latest.tmp/image.json: no such file or directory, the other's sandbox could not execute /bin/sh.
An import now holds an exclusive flock on $WERKDOCK_HOME/locks/import-<hash>.lock, across goroutines and processes; the kernel drops it when the holder dies.
The import that waited refuses with already exists, as an import after it would.
TestConcurrentImportsOfOneImageLeaveOneCompleteImage: four imports of a 3000-file archive at once leave one success, three refusals and one complete image. It failed 3 of 3 runs without the lock.
werkdock run answers 127 or 126
engine.CheckCommand looks the command up like execvp inside the rootfs (symlinks resolved in the rootfs, PATH in order, a non-executable candidate skipped for a later one). run refuses a command that cannot start with docker's 127 (not found) or 126 (not executable) before anything is created; until now bwrap's 1 came back.
Paths under a mount, the implicit tmpfs areas included, are not checked. create/start and the Docker API are unchanged.
The resolver moved from store to its own package rootpath, shared with engine.
Smoke-tested with the built binary: run smoke missing exits 127, a file without execute bit 126, a second import 125 with already exists.
Found on mih09 with Werkator 1.37.0 (repository werkrevisor, PR#5): a CI build and a pull-request review started together and both imported the same new rootfs.
## Imports of one image wait for each other
`Store.Import` stages in `<image>.tmp` and removes that directory first, so the next import cleans up after an interrupted one.
A second import at the same time removed the first one's files: one failed with `latest.tmp/image.json: no such file or directory`, the other's sandbox could not execute `/bin/sh`.
An import now holds an exclusive `flock` on `$WERKDOCK_HOME/locks/import-<hash>.lock`, across goroutines and processes; the kernel drops it when the holder dies.
The import that waited refuses with `already exists`, as an import after it would.
`TestConcurrentImportsOfOneImageLeaveOneCompleteImage`: four imports of a 3000-file archive at once leave one success, three refusals and one complete image. It failed 3 of 3 runs without the lock.
## `werkdock run` answers 127 or 126
`engine.CheckCommand` looks the command up like execvp inside the rootfs (symlinks resolved in the rootfs, PATH in order, a non-executable candidate skipped for a later one).
`run` refuses a command that cannot start with docker's 127 (not found) or 126 (not executable) before anything is created; until now bwrap's 1 came back.
Paths under a mount, the implicit tmpfs areas included, are not checked. `create`/`start` and the Docker API are unchanged.
The resolver moved from `store` to its own package `rootpath`, shared with `engine`.
Smoke-tested with the built binary: `run smoke missing` exits 127, a file without execute bit 126, a second import 125 with `already exists`.
Version 0.11.0 with its CHANGELOG section.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Two imports of the same image removed each other's staging directory,
seen with two Werkator builds that started together on mih09. An import
now holds an exclusive flock per image below $WERKDOCK_HOME/locks.
werkdock run looks the command up like execvp inside the rootfs and
answers docker's 127 or 126 when it cannot be started, instead of
bwrap's 1. Paths under a mount are not checked. The rootfs path resolver
moved from store into its own package rootpath, shared with engine.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
mi
changed title from WIP: One import per image at a time, and run answers 126/127 for a command that cannot start to One import per image at a time, and run answers 126/127 for a command that cannot start (0.11.0)2026-10-05 17:02:26 +02:00
mi
merged commit ea30cbe79c into main2026-10-05 17:29:21 +02:00
mi
deleted branch mihoe/import-lock2026-10-05 17:29:21 +02:00
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Found on mih09 with Werkator 1.37.0 (repository werkrevisor, PR#5): a CI build and a pull-request review started together and both imported the same new rootfs.
Imports of one image wait for each other
Store.Importstages in<image>.tmpand removes that directory first, so the next import cleans up after an interrupted one.A second import at the same time removed the first one's files: one failed with
latest.tmp/image.json: no such file or directory, the other's sandbox could not execute/bin/sh.An import now holds an exclusive
flockon$WERKDOCK_HOME/locks/import-<hash>.lock, across goroutines and processes; the kernel drops it when the holder dies.The import that waited refuses with
already exists, as an import after it would.TestConcurrentImportsOfOneImageLeaveOneCompleteImage: four imports of a 3000-file archive at once leave one success, three refusals and one complete image. It failed 3 of 3 runs without the lock.werkdock runanswers 127 or 126engine.CheckCommandlooks the command up like execvp inside the rootfs (symlinks resolved in the rootfs, PATH in order, a non-executable candidate skipped for a later one).runrefuses a command that cannot start with docker's 127 (not found) or 126 (not executable) before anything is created; until now bwrap's 1 came back.Paths under a mount, the implicit tmpfs areas included, are not checked.
create/startand the Docker API are unchanged.The resolver moved from
storeto its own packagerootpath, shared withengine.Smoke-tested with the built binary:
run smoke missingexits 127, a file without execute bit 126, a second import 125 withalready exists.Version 0.11.0 with its CHANGELOG section.
🤖 Generated with Claude Code
WIP: One import per image at a time, and run answers 126/127 for a command that cannot startto One import per image at a time, and run answers 126/127 for a command that cannot start (0.11.0)