fix: trust archive permissions instead of file(1) heuristics in ziextract#771
Merged
pschmitt merged 1 commit intoMar 14, 2026
Merged
Conversation
alberti42
requested review from
alichtman,
pschmitt and
vladdoster
as code owners
March 12, 2026 19:56
alberti42
force-pushed
the
fix-executable-permissions
branch
from
March 13, 2026 08:54
5ca3039 to
12f2487
Compare
pschmitt
approved these changes
Mar 14, 2026
github-actions Bot
pushed a commit
that referenced
this pull request
Jul 1, 2026
# [3.15.0](v3.14.0...v3.15.0) (2026-07-01) ### Bug Fixes * .zi-check-for-git-changes runs git fetch before checking for changes ([1107460](1107460)) * add markdownlint exceptions to linting ([507b4e5](507b4e5)) * avoid apostrophe that breaks typeset -f round-trip of :zinit-tmp-subst-autoload ([#783](#783)) ([d2d4237](d2d4237)) * correct SSH URL format for private repos ([#721](#721)) ([991e4a8](991e4a8)) * corrected print-out message in self-update routine ([bb37f3b](bb37f3b)) * impl gh-r version detection using GitHub API ([#731](#731)) ([f1e503d](f1e503d)), closes [#r](https://github.com/zdharma-continuum/zinit/issues/r) * prevent "permission denied" errors from empty hook handlers ([#729](#729)) ([2167408](2167408)) * prevent double extraction when exclamation mark modifier is used in extract'!' ([4fd38b3](4fd38b3)), closes [#r](https://github.com/zdharma-continuum/zinit/issues/r) [#r](https://github.com/zdharma-continuum/zinit/issues/r) * remove `local -A OPTS` that shadows caller's parallel flag ([#774](#774)) ([e88ed77](e88ed77)) * resolve a bug where the `-q` option for quiet mode was never dispatched ([6cf2f29](6cf2f29)) * resolve a second issue where `git -C <path>` had to be used instead of `git --work-tree <path>` ([2e83176](2e83176)) * resolves a bug with github action trying to push updated docs back to external forks ([#763](#763)) ([34a1811](34a1811)) * silenced `zinit self-update` when in quiet mode (`-q`) ([8d154b0](8d154b0)) * trust archive permissions instead of `file(1)` heuristics in `ziextract` ([#771](#771)) ([1334994](1334994)) * update `git-chglog` to `git-cliff` in gh-r tests ([18fc2cc](18fc2cc)), closes [#r](https://github.com/zdharma-continuum/zinit/issues/r) * Update various tools in gh-r tests ([#759](#759)) ([01eb65f](01eb65f)), closes [#r](https://github.com/zdharma-continuum/zinit/issues/r) ### Features * validate ice arguments ([#778](#778)) ([424b241](424b241)) * **zsh:** allow configuring settings like HOME_DIR/BIN_DIR via zstyle ([8f89f88](8f89f88))
|
🎉 This PR is included in version 3.15.0 🎉 The release is available on GitHub release Your semantic-release bot 📦🚀 |
7 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
This PR fixes a bug in
ziextract, the function responsible for unpacking archiveswhen installing plugins via
from'gh-r'or any tarball download.The original developer added a well-intentioned feature: after extracting an archive,
zinit would automatically detect which files are executables and set the
+xpermissionon them, so the user would not have to do it manually. To detect executables, the code
used the standard Unix utility
file(1), which can inspect file contents and reportwhat kind of file each one is.
The problem is that
file(1)does not actually tell you whether a file should beexecutable. It tells you what kind of content a file contains. In practice,
file(1)labels any file containing common Python syntax — including documentation files, Vim
scripts, and test data — as
"Python script text executable". Zinit's code then seesthe word
"executable"in that output and dutifully applieschmod +xto all of them,including files that have no business being executable at all.
Reproduction — install Neovim from its GitHub nightly release:
The archive contains exactly 9 files with the execute bit set (
bin/nvim,lib/nvim/parser/*.so,share/nvim/runtime/scripts/less.sh). After installation,fd -t=xreveals that many additional files have been marked executable:Root Cause
The
file(1)magic database emits"Python script text executable"for any filewhose content matches common Python syntax patterns —
import,class,def,try/except— regardless of shebangs or filesystem permissions. A Vim documentationfile (
if_pyth.txt) that merely documents the Python interface is enough to trigger it:The word
"executable"infile(1)output means "this is a file written in alanguage that can be run" — not "this file should have the execute permission bit".
Zinit's filter was asking the wrong question. To make matters worse,
file(1)appliesthis label even to Python files with no shebang line — files that the kernel would
refuse to execute directly anyway, since there is no interpreter specified.
Fix
Replace the
file(1)-based detection with a single glob that collects only fileswhose execute bit is already set in the archive:
This is the authoritative signal: if the package author wanted a file executable, they
set
+xwhen creating the archive. Any file that lacks+xin the archive — librarymodule, documentation, data file — should stay non-executable regardless of its
content. If a plugin or app is packaged with wrong permissions — rare but possible —
the best course of action is for the user to correct them via
atclone/atpull,rather than having zinit apply heuristics that will assign wrong permissions far more
often than they fix anything.
Discussion: shebang-based detection
An alternative considered during this fix is to additionally mark executable any file
that has a shebang (
#!) but was not+xin the archive — to compensate for packagesthat forget to set permissions. Eventually, this was deliberately left out for two reasons:
+x. The#!/hint/zshpattern, common in the zinit ecosystem, is used purely as an editorsyntax hint and is explicitly not meant to make a file executable.
best course of action is for the user to correct them via
atclone/atpull,rather than having zinit apply heuristics that will assign wrong permissions far
more often than they fix anything.
Feedback on this decision is welcome.