Skip to content

feat(cli): add curl installer channel with checksum verification - #799

Merged
benvinegar merged 7 commits into
mainfrom
claude/opencode-update-mechanism-0m7hl7
Aug 18, 2026
Merged

feat(cli): add curl installer channel with checksum verification#799
benvinegar merged 7 commits into
mainfrom
claude/opencode-update-mechanism-0m7hl7

Conversation

@benvinegar

@benvinegar benvinegar commented Aug 18, 2026

Copy link
Copy Markdown
Member

Hunk gets a one-line installer:

curl -fsSL https://hunk.dev/install.sh | sh

It figures out your platform (macOS/Linux, Intel/ARM, including Rosetta-translated shells), downloads the matching release archive, verifies it against the release's checksums, and puts everything under ~/.hunk with the bin directory added to your PATH. After that, hunk update keeps it current — the same way it already does for npm and Homebrew installs.

How it works

The script installs into ~/.hunk/bin, with the bundled agent skills next to the binary so hunk skill path keeps working. Pin a version with HUNK_VERSION=0.19.0 (or pass it as an argument), pick a different directory with HUNK_INSTALL_DIR, and skip the shell-rc edit with --no-modify-path.

hunk update recognizes a curl install by its ~/.hunk/bin location, checks GitHub releases for the newest version, and updates by re-running the installer with the version pinned — so there's exactly one code path that knows how to install Hunk, and it's the one users can read.

Safety

Curl-pipe installers have a bad reputation, so this one is defensive about the classic failure modes:

  • Truncated downloads can't run half a script. Everything lives in a main function called on the last line; a cut-off stream dies on a syntax error instead of executing a prefix. (hunk update goes further and downloads the script fully before executing it.)
  • Checksums are verified. The release workflow now publishes a SHA256SUMS asset (attested like the archives), and the script refuses a mismatched or missing entry. Releases from before the asset existed install with a warning instead of a silent skip.
  • Interrupts can't wreck an existing install. The skills tree swaps via renames with a cleanup trap that restores the old tree if you ctrl-C at exactly the wrong moment, and the binary lands through an atomic same-directory rename.
  • Weird paths stay paths. The PATH line written to your shell rc single-quotes the directory, so spaces, $(...), or apostrophes in an install path can't turn into code at shell startup.

Testing

  • scripts/install-sh.test.ts checks the script in the normal test suite: POSIX syntax, release-asset naming kept in sync with the publish workflow, the platform-detection matrix (with stubbed uname), and a guard that the main-on-last-line defense stays in place.
  • A new Install script E2E workflow runs the real thing against the latest published release on ubuntu + macos: fresh install, idempotent re-run, custom directory with spaces, and a loud failure for a nonexistent version. It triggers when the script changes, weekly, and on demand — it'll run for the first time on this PR.
  • All of the above was also run by hand in a sandbox against v0.19.0, including hunk skill path resolution for both default and custom directories.

Worth knowing

  • hunk.dev/install.sh (and /install) go live when the website deploys after merge.
  • SHA256SUMS starts existing with the first release cut after this merges — until then installs print the "no checksums yet" warning by design.
  • A custom-HUNK_INSTALL_DIR install can't be auto-detected by hunk update later (the env var is gone once your shell exits); the installer tells you to re-run it with the same directory instead.
  • Windows isn't covered by the script — it points at npm install -g hunkdiff.

🤖 Generated with Claude Code

https://claude.ai/code/session_01Sf2y1jWD9fgx7aAKYbLQC6

claude added 2 commits August 18, 2026 01:13
Ship a hosted install script (hunk.dev/install.sh) that resolves the
platform archive from GitHub releases, verifies it against a new
SHA256SUMS release asset, and lays the binary and bundled skills out
under ~/.hunk so skill resolution keeps working. Teach the update seam
a curl install source: detected from the .hunk/bin executable path,
version-checked against GitHub releases, and updated by re-running the
installer with the target version pinned, so curl installs get the
same self-update behavior as npm and Homebrew instead of a manual
download.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Sf2y1jWD9fgx7aAKYbLQC6
Wrap the install script in a main function invoked on its last line so
a truncated curl-to-sh stream dies on a syntax error instead of
executing a prefix of the install, swap the bundled skills through
renames so an existing install never has a window with no skills,
exit explicitly on INT/TERM so an interrupted run cannot resume past
its own cleanup, fall back to wget when hunk update re-runs the
installer on a curl-less machine, and correct the docs and detection
comment for custom-directory installs, which are not auto-detectable
once the installing shell exits — the installer now says so and
prints the re-run command instead.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Sf2y1jWD9fgx7aAKYbLQC6
@vercel

vercel Bot commented Aug 18, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
hunk-web Ignored Ignored Preview Aug 18, 2026 1:59pm

Request Review

@greptile-apps

greptile-apps Bot commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds a hosted macOS/Linux installer with release checksums and integrates that install source with update detection and hunk update.

  • Generates and publishes a SHA256SUMS release asset.
  • Installs prebuilt binaries and bundled skills under ~/.hunk, with custom-directory and PATH options.
  • Detects curl installs, queries GitHub releases, and re-runs the installer for updates.
  • Adds installer, update, detection, CLI, and documentation coverage.

Confidence Score: 2/5

This PR should not merge until failed installer downloads are reported correctly and installer interruption or custom path syntax can no longer leave users with a falsely successful or broken installation.

The new update pipeline can report success without downloading anything, while the installer can lose the canonical skills tree during interruption and writes unescaped custom paths into shell startup files.

Files Needing Attention: src/core/process/selfUpdate.ts and website/public/install.sh

Important Files Changed

Filename Overview
website/public/install.sh Adds the full installer, but its interrupted skills swap and unescaped startup-file path serialization can leave installations broken.
src/core/process/selfUpdate.ts Adds curl-based self-update execution, but downloader failures are masked by the fetch-to-shell pipeline.
src/core/process/installSource.ts Adds curl install-source detection from the default .hunk/bin layout; the documented custom-directory detection boundary remains.
src/core/process/latestRelease.ts Adds stable-version lookup through GitHub's latest-release endpoint with the existing timeout and failure conventions.
.github/workflows/release-prebuilt-npm.yml Generates a checksum manifest for all release archives and includes it in existing attestation and upload globs.
scripts/install-sh.test.ts Covers syntax, platform mapping, archive naming, bundled-skill layout, and truncation structure, but not the failing update and interruption paths identified above.

Sequence Diagram

sequenceDiagram
  participant U as User
  participant H as hunk update
  participant G as GitHub API
  participant S as hunk.dev/install.sh
  participant R as GitHub Release
  participant F as Local Filesystem
  U->>H: hunk update
  H->>G: Resolve latest release
  G-->>H: Version
  H->>S: Download installer via curl/wget
  S->>R: Download archive and SHA256SUMS
  R-->>S: Release assets
  S->>S: Verify checksum and extract
  S->>F: Replace skills and binary
  S->>F: Optionally update shell PATH
  H-->>U: Report update result
Loading
Prompt To Fix All With AI
### Issue 1
src/core/process/selfUpdate.ts:161-163
**Downloader failures report success**

When `curl` or `wget` cannot fetch the installer, the downstream `sh` receives empty input and exits successfully because the pipeline preserves only its status. `hunk update` consequently suppresses the downloader error and prints that the target version was installed even though the binary was unchanged.

### Issue 2
website/public/install.sh:300-304
**Interrupted swap removes skills**

When the installer is interrupted after moving `skills` to `skills.old` but before promoting `skills.new`, cleanup removes only the temporary download directory and never restores the old tree. The existing binary remains installed without its canonical skills directory, breaking bundled-skill resolution until another successful installation repairs it.

### Issue 3
website/public/install.sh:315
**Custom paths become shell syntax**

When `HUNK_INSTALL_DIR` contains shell-significant characters, the installer writes that value directly into executable POSIX or fish startup syntax. On the next shell startup the directory is expanded or parsed instead of treated literally, leaving Hunk off `PATH` and allowing command syntax embedded in the path to execute.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Reviews (1): Last reviewed commit: "fix(cli): harden curl installer against ..." | Re-trigger Greptile

Comment thread src/core/process/selfUpdate.ts Outdated
Comment thread install.sh
Comment thread website/public/install.sh Outdated
Download the installer to a file before executing it so a failed fetch
surfaces its own error instead of feeding sh empty input and reporting
a successful update, restore the parked skills tree from cleanup when
an interrupted swap left it aside, and write the PATH line with a
single-quoted directory so a custom install path containing
shell-significant characters stays a literal path in shell startup
files instead of becoming code.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Sf2y1jWD9fgx7aAKYbLQC6
Match the exact line the installer writes instead of the raw
directory, which no longer appears verbatim once quoting escapes it,
so re-running the installer against a path containing shell-special
characters cannot append duplicate PATH blocks. Also escape the dot in
the vercel header route so it matches only the literal install.sh
path.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Sf2y1jWD9fgx7aAKYbLQC6
claude added 2 commits August 18, 2026 02:26
Exercise the real download, checksum, extract, skill-resolution, and
PATH flow on ubuntu and macos whenever the script changes, weekly to
catch release-asset drift, and on demand. The release version is
resolved with an authenticated gh call so shared-runner API rate
limits cannot fail runs the script did not cause.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Sf2y1jWD9fgx7aAKYbLQC6
The install script is a product artifact with its own tests, CI, and
release-pipeline contract, not site content, so it moves from
website/public/ to the repository root where contributors and
read-before-you-pipe users can find it. The website build stages it
into the deploy output as its final step, keeping the served copy in
lockstep with the same deploy, and the vercel ignore rule now includes
it so script changes still trigger a site deploy.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Sf2y1jWD9fgx7aAKYbLQC6
Move install-source detection, per-channel release lookup, and the
hunk update execution out of core/process into a core/install module:
they are one feature family about how the binary was installed and how
it gets replaced, not about the process a run lives in, and they only
landed in process because the startup update notice lived there. The
notice stays in core/process as a startup-notice producer built on the
app-state file and consumes core/install. Path-only move; no exported
symbol changed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Sf2y1jWD9fgx7aAKYbLQC6
@benvinegar
benvinegar merged commit 61ca92d into main Aug 18, 2026
15 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants