You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
File permission bits in Kit not preserved when copied to sandbox #499
I believe this worked with sbx v0.35.0, which was the release at the time I tested the behavior and started using it.
This seems to have changed when I create sandboxes with the current version:
Hey @rhcarvalho, yes sorry, that's effectively a ~regression in spec v2, to align kit building on macOS and Windows to create the same kit artifacts. It's clearly not ideal for certain use cases, sorry for the inconvenience, we'll think how to deal with this going forward.
Thanks for the precise report — this was an intentional change, but the consequence for kits shipping executable scripts was neither intended nor documented. Your version bisect is exactly right.
What changed
Two separate pins are involved:
Since v2 support landed (well before v0.35.0), the v2 layer packer stamps every file entry with mode 0644. On-disk permission bits are discarded at pack time.
In v0.39.0 that pin was extended to local-directory and git loads, which until then kept the author's on-disk mode.
Your kit is git-sourced, so on v0.35.0 it took path 2 and the 0755 survived — while the same kit pushed to a registry and pulled back would already have been 0644. v0.39.0 closed that inconsistency so a kit injects identically whether it comes from git, a local directory, or an OCI pull. Staying on schemaVersion: "1" would have avoided it, since the pin is v2-only.
Why the mode is pinned
The v2 layer is content-addressed and signable, so its tar bytes have to be reproducible from the source tree alone. On-disk modes aren't: they vary with the author's umask, and on Windows Go cannot report POSIX bits at all — os.Stat derives permissions from the read-only attribute alone, so every writable file reads as 0666 and a regular file never carries an execute bit. If the packer honored on-disk modes, the same kit packed on Windows and on Linux would produce different layer digests for every file, not just scripts.
The trade-off is that v2 has no way today to declare a per-file executable bit for files/.
Workaround
setup.install runs after files/home/ is staged, so a chmod there works:
setup:
install:
- command: "chmod 0755 /home/agent/.gemini/antigravity-cli/statusline.sh"user: "0"description: Restore the executable bit dropped by the v2 file-mode pin
Verified against a real sandbox: without the install command the script lands 0644 and is not executable by the agent user; with it, 0755 and it runs.
Two caveats:
This works for files/home/ only. files/workspace/ is written after the workspace is populated, which is after install commands run, so the same chmod against a workspace path would fail.
Install-command failures are non-fatal at create time, so a wrong path leaves you with a still-0644 file rather than a hard error.
On our side
You're right that this isn't documented — SPEC-v2.md §5.8 covers targets, path rules and composition but says nothing about permission bits, so there was no way to anticipate this from the spec. That's a gap worth fixing, along with the question of whether files/ should gain an explicit mode declaration so kits don't need the chmod workaround.
@cdupuis thanks for the details, it makes perfect sense.
Install-command failures are non-fatal at create time, so a wrong path leaves you with a still-0644 file rather than a hard error.
Oh, I was not aware. This is also not documented.
I'd certainly rather the install command failed and I get to fix it than silently creating the sandbox in an inconsistent state. This would be specially true if one uses multiple commands in the setup.install list. If ignoring exit status was desired, kit authors could do so explicitly.
@rhcarvalho FWIW, I think the install step approach is a great one for now and very portable 👍
In the recent past I've inlined files from files/home/ into spec.yaml, then eventually reverted that change. The original motivation was to centralize what goes into the new sandbox and control permissions, because setup.files supports explicit mode.
Only one of the files I generate depends on the ${WORKDIR} placeholder replacement. I decided to revert the inlining because editing/maintaining larger files inside of yaml was too inconvenient.
So one idea I can offer is to expand the setup.files spec to allow pointing to the file contents relative to the spec.yaml file. This way another solution to the permission issue would be:
This is similar to the Docker Compose spec that originally only supported pointing to a Dockerfile file, then later gained the inline form. In our case here, setup.files support the inline form, but not pointing to a file in disk for the contents.
Activity
kiview commentedon Aug 24, 2026
Hey @rhcarvalho, yes sorry, that's effectively a ~regression in spec v2, to align kit building on macOS and Windows to create the same kit artifacts. It's clearly not ideal for certain use cases, sorry for the inconvenience, we'll think how to deal with this going forward.
rhcarvalho commentedon Aug 24, 2026
@kiview thanks for confirming.
FWIW I've worked around the issue by adding a
setup.installstep to explicitly set the permissions.praialabs/sbx-kits@4e3d6cf
cdupuis commentedon Aug 24, 2026
Thanks for the precise report — this was an intentional change, but the consequence for kits shipping executable scripts was neither intended nor documented. Your version bisect is exactly right.
What changed
Two separate pins are involved:
0644. On-disk permission bits are discarded at pack time.Your kit is git-sourced, so on v0.35.0 it took path 2 and the
0755survived — while the same kit pushed to a registry and pulled back would already have been0644. v0.39.0 closed that inconsistency so a kit injects identically whether it comes from git, a local directory, or an OCI pull. Staying onschemaVersion: "1"would have avoided it, since the pin is v2-only.Why the mode is pinned
The v2 layer is content-addressed and signable, so its tar bytes have to be reproducible from the source tree alone. On-disk modes aren't: they vary with the author's umask, and on Windows Go cannot report POSIX bits at all —
os.Statderives permissions from the read-only attribute alone, so every writable file reads as0666and a regular file never carries an execute bit. If the packer honored on-disk modes, the same kit packed on Windows and on Linux would produce different layer digests for every file, not just scripts.The trade-off is that v2 has no way today to declare a per-file executable bit for
files/.Workaround
setup.installruns afterfiles/home/is staged, so a chmod there works:Verified against a real sandbox: without the install command the script lands
0644and is not executable by the agent user; with it,0755and it runs.Two caveats:
files/home/only.files/workspace/is written after the workspace is populated, which is after install commands run, so the same chmod against a workspace path would fail.0644file rather than a hard error.On our side
You're right that this isn't documented — SPEC-v2.md §5.8 covers targets, path rules and composition but says nothing about permission bits, so there was no way to anticipate this from the spec. That's a gap worth fixing, along with the question of whether
files/should gain an explicit mode declaration so kits don't need the chmod workaround.kiview commentedon Aug 24, 2026
@rhcarvalho FWIW, I think the install step approach is a great one for now and very portable 👍
rhcarvalho commentedon Aug 24, 2026
@cdupuis thanks for the details, it makes perfect sense.
Oh, I was not aware. This is also not documented.
I'd certainly rather the install command failed and I get to fix it than silently creating the sandbox in an inconsistent state. This would be specially true if one uses multiple commands in the
setup.installlist. If ignoring exit status was desired, kit authors could do so explicitly.rhcarvalho commentedon Aug 24, 2026
In the recent past I've inlined files from
files/home/intospec.yaml, then eventually reverted that change. The original motivation was to centralize what goes into the new sandbox and control permissions, becausesetup.filessupports explicitmode.Only one of the files I generate depends on the
${WORKDIR}placeholder replacement. I decided to revert the inlining because editing/maintaining larger files inside ofyamlwas too inconvenient.So one idea I can offer is to expand the
setup.filesspec to allow pointing to the file contents relative to thespec.yamlfile. This way another solution to the permission issue would be:spec.yaml:(Didn't think the name
sourcethrough.)This is similar to the Docker Compose spec that originally only supported pointing to a Dockerfile file, then later gained the inline form. In our case here,
setup.filessupport the inline form, but not pointing to a file in disk for the contents.rltas commentedon Aug 24, 2026
I came across this because it broke https://github.com/docker/sbx-kits-contrib/tree/main/claude-sbx-statusline and potentially other community contributed kits as well.
feat(kits): synthesize chmod from hybrid/v1 mode fields into generate…
feat(kits): synthesize chmod from hybrid/v1 mode fields into generate…