Skip to content

fix(loader): harden install and align XDG paths - #197

Merged
ss-o merged 2 commits into
mainfrom
code/init-loader-hardening
Aug 28, 2026
Merged

fix(loader): harden install and align XDG paths#197
ss-o merged 2 commits into
mainfrom
code/init-loader-hardening

Conversation

@ss-o

@ss-o ss-o commented Aug 28, 2026

Copy link
Copy Markdown
Member

Closes #194. Closes #195. Closes #196.

Parent: z-shell/.github#553
Project: Z-shell Delivery

What this changes

This PR repairs the published clean-machine loader, preserves Zi's intended sourced global effects, hardens temporary execution and clone failure handling, and reconciles installer path selection with the finalized Zi core resolver.

  • Fixes the progress-filter URL and makes the cosmetic filter non-fatal.
  • Preserves caller parsing options without discarding Zi's intentional global effects.
  • Uses the Git pipeline status, validates ZI[STREAM], narrows permissions changes, and keeps retry helpers on failure.
  • Uses a private temporary directory for the downloaded progress filter.
  • Makes loader history defaults opt-in through ZI[LOADER_HISTORY].
  • Mirrors core home selection before zi.zsh exists: explicit override, recognized legacy home, valid absolute XDG data home, then the specification fallback.
  • Leaves cache and config resolution to Zi core instead of duplicating defaults.
  • Rejects relative XDG data/config values in the POSIX installer and avoids parallel installs when a legacy or uniquely identified XDG checkout already exists.
  • Keeps generated checksums and loader documentation synchronized.

No user data is migrated or deleted.

Verification

  • sh ./tests/installers.sh: 20/20 assertions pass.
  • POSIX installer and native Zsh syntax checks pass.
  • sha256sum -c public/checksum.txt passes for all four published assets.
  • git diff --check passes.

The loader downloaded its git progress filter from
lib/zsh/git-process-output.zsh under a public/zsh/ path that does not
exist in z-shell/zi. The fetch returned 404, _zi_setup returned 1 with no
diagnostic, and every clean-machine `source init.zsh && zzinit` aborted
before cloning. Verified against the published endpoint before the fix.

Correctness

- Point the progress-filter URL at the published lib/zsh path and treat
  the filter as cosmetic: a failed fetch now falls back to plain output
  instead of aborting the install.
- Stop wrapping the zi.zsh source in `emulate -L zsh`. zi.zsh's top level
  intentionally sets AUTO_CD and marks path, manpath, cdpath, mailpath,
  fpath, and logpath exported and unique; `-L` localized those to the
  loader's own function and discarded them. Guard only the options that
  would corrupt zi.zsh's parsing, then restore the caller's values.
- Read `${pipestatus[1]}` for the clone status. The previous code
  inspected the pipeline's exit status, which reported the progress
  filter rather than git.

Defaults ownership

- Keep only the settings that must exist before Zi does: REPOSITORY,
  STREAM, HOME_DIR, BIN_DIR, CACHE_DIR, CONFIG_DIR, MUTE_WARNINGS.
  CACHE_DIR and CONFIG_DIR stay because zi.zsh's own fallbacks are not
  XDG-first: they prefer $HOME/.cache and $HOME/.config when those exist
  and only then consult the XDG variables.
- Drop the ZPFX, ZMODULES_DIR, and ZCOMPDUMP_PATH duplicates. zi.zsh
  derives them identically, and resolved paths are unchanged.
- Document the deliberate HOME_DIR divergence between the two entry
  points instead of leaving it implicit.

Security and diagnostics

- Download the executed progress filter into a `mktemp -d` directory. The
  previous fixed ${TMPDIR}/zi path let another user pre-place a file that
  the loader would chmod +x and run, and the [[ ! -f ]] guard skipped the
  download entirely when one was already there.
- Validate ZI[STREAM] with `git check-ref-format` before it reaches
  `git clone --branch`, rejecting option-like values.
- Scope `chmod -R go-w` to BIN_DIR rather than all of HOME_DIR, which
  also holds plugins, snippets, and other user data.
- Report a diagnostic on every failure path, including the missing
  curl/wget and missing git cases that previously failed silently.
- Keep zzinit and its helpers defined when a run fails so the user can
  fix the cause and retry; only unset them on success.
- Sequence completion registration and zpmod independently so one
  failure no longer skips the other.
- Add `typeset -gU module_path` before appending, and surface a zmodload
  failure unless MUTE_WARNINGS is set.
- Gate the history defaults behind ZI[LOADER_HISTORY]; loading a plugin
  manager should not create directories and touch files by default.

Clone cost

- Use `--filter=blob:none --single-branch` in both the loader and the
  installer's minimal .zshrc profile. The installer previously used
  `--depth=1`, which degrades ZI[VERSION] from a `git describe` tag to a
  bare short SHA. Verified: v1.1.0-83-g1e41c12 preserved, clone 1.8M.

CI

- The "init.zsh sync drift" step passed --local and --remote as the same
  checked-out file, so it could never detect drift. Replace it with a
  scheduled loader-drift workflow that compares repository source against
  both the published init.zshell.dev endpoint and raw main.

Tests

- Cover caller-option preservation, strict XDG paths, the history opt-out,
  ZI[STREAM] rejection, the progress-filter URL, and the private temporary
  directory. Suite passes 16/16; zsh-lint clean under the configured
  profile.
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Aug 28, 2026

Copy link
Copy Markdown

Deploying src with  Cloudflare Pages  Cloudflare Pages

Latest commit: b5081fa
Status: ✅  Deploy successful!
Preview URL: https://c98a5ba6.zi-src.pages.dev
Branch Preview URL: https://code-init-loader-hardening.zi-src.pages.dev

View logs

@ss-o

ss-o commented Aug 28, 2026

Copy link
Copy Markdown
Member Author

Context for reviewers: Zi is currently implementing XDG-first path resolution.

This PR keeps the loader's ZI[CACHE_DIR] and ZI[CONFIG_DIR] assignments, which are needed only because today's released zi.zsh prefers $HOME/.cache and $HOME/.config when those exist and consults the XDG variables only otherwise. Once the XDG-first work ships, those two assignments become redundant rather than wrong: both sides resolve to the same paths, so the loader keeps working unchanged and nothing here needs to land in a particular order.

The in-code comment and docs/README.md both name the removal condition explicitly (released zi.zsh resolving both XDG-first) and ask that test_init_xdg_paths_are_strict be kept either way, so the expected paths stay pinned regardless of which side assigns them.

ZI[HOME_DIR] is the same story: the loader already defaults to the XDG data path, which is the intended destination, so convergence removes a divergence rather than creating one.

@ss-o ss-o changed the title fix(loader): repair clean-machine install and harden init.zsh fix(loader): harden install and align XDG paths Aug 28, 2026
@ss-o ss-o added area:zi Zi core behavior, APIs, or documentation. type:bug Something is broken or behaving incorrectly. labels Aug 28, 2026
@ss-o
ss-o merged commit 99584e7 into main Aug 28, 2026
9 checks passed
@ss-o
ss-o deleted the code/init-loader-hardening branch August 28, 2026 03:58
github-actions Bot pushed a commit that referenced this pull request Aug 28, 2026
Co-authored-by: Sal <ss-o@users.noreply.github.com> 99584e7
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:zi Zi core behavior, APIs, or documentation. type:bug Something is broken or behaving incorrectly.

Projects

None yet

1 participant