1) Overview
This script downloads a prebuilt Claude Code CLI binary for your OS/CPU from a Google Cloud Storage bucket, verifies it with a SHA-256 checksum from a manifest, marks it executable, then runs the binary’s own install subcommand to perform the actual installation/shell integration. Finally it deletes the downloaded binary.
2) Step-by-step breakdown (by section / function)
Strict mode / argument handling
set -e(line 3): aborts the script on most command failures.TARGET="$1"(line 6): optional argument passed through to the CLI installer (stable|latest|VERSION).- Target validation (lines 9–13): if
TARGETis provided, it must match: stableorlatest, or- semver like
1.2.3or1.2.3-suffixOtherwise it exits with a usage message.
Constants and local download location
- GCS bucket base URL (line 15):
https://storage.googleapis.com/.../claude-code-releases - Download directory (line 16):
~/.claude/downloads
Dependency checks
- Downloader selection (lines 19–29): requires either
curlorwget. Preferscurlif present. - Optional
jq(lines 32–36): if installed, used to parse JSON safely; otherwise it falls back to a regex parser.
Download helper
download_file(url, output)(lines 39–63):- Uses
curl -fsSL(quiet-ish, fail on HTTP errors, follow redirects) orwget -q. - If
outputis provided, saves to file; otherwise prints to stdout.
JSON checksum extraction (fallback)
get_checksum_from_manifest(json, platform)(lines 66–82):- Normalizes JSON into one line (line 70).
- Uses a bash regex to extract a 64-hex-character checksum for a given platform key (line 73).
- This is less robust than
jqbut constrained to matching a SHA-256-like pattern.
Platform detection
- OS detection via
uname -s(lines 85–89): Darwin→darwinLinux→linux- Other → exit (“Windows is not supported”)
- Architecture via
uname -m(lines 91–94): x86_64/amd64→x64arm64/aarch64→arm64- Rosetta handling (macOS) (lines 99–104):
- If running under Rosetta (
sysctl.proc_translated=1), it switches to downloading arm64 even ifuname -mreports x64. - musl vs glibc (Linux) (lines 107–114):
- Detects musl via presence of musl libc files or
ldd /bin/ls | grep musl. - Sets platform to
linux-${arch}-muslorlinux-${arch}accordingly. - Creates download directory (line 118):
mkdir -p ~/.claude/downloads
Version + manifest retrieval
- Always fetches “latest” version string (line 121):
version=$(download_file "$GCS_BUCKET/latest")
Note: Despite the comment and TARGET support, this script always uses the “latest” file to decide what binary to download;TARGETis only passed later toclaude install. - Downloads manifest.json for that version (line 124):
.../$version/manifest.json - Extract checksum:
- with
jq(line 128):.platforms["$platform"].checksum - otherwise with
get_checksum_from_manifest(line 130)
Checksum validation and binary download
- Verifies checksum looks like SHA-256 hex (lines 134–138). If not, exits “Platform … not found in manifest”.
- Downloads the binary to a versioned path (lines 141–147):
~/.claude/downloads/claude-$version-$platformfrom
.../$version/$platform/claude - Computes SHA-256:
- macOS:
shasum -a 256(line 153) - Linux:
sha256sum(line 155) - Compares computed vs manifest checksum (lines 158–163); deletes file and exits on mismatch.
- Marks executable (line 166):
chmod +x
Executes the downloaded installer
- Runs:
"$binary_path" install ${TARGET:+"$TARGET"}(line 170) - This is the key action: it executes newly-downloaded code and delegates all “real installation” tasks to it.
- Deletes the downloaded binary after install (line 173).
- Prints completion message (lines 175–178). (The checkmark glyph appears mis-encoded as
âin your pasted script.)
3) What it installs/modifies
Directly, this shell script:
- Creates directory:
- ~/.claude/downloads (line 118)
- Writes and then deletes a downloaded binary:
- ~/.claude/downloads/claude-$version-$platform (lines 141–147, 173)
- Executes that binary’s install command (line 170)
Everything else depends on what claude <version> install does, which is not shown here. Based on the wording (“launcher and shell integration”, line 168), likely changes include:
- Installing a launcher somewhere on PATH (commonly ~/.local/bin, /usr/local/bin, etc.)
- Modifying shell init files (e.g., ~/.bashrc, ~/.zshrc, ~/.profile) to add PATH entries or completions
- Creating additional ~/.claude/... state/config directories
But those are not visible in this script, because it hands off installation to the downloaded binary.
4) Network activity
All network requests go to Google Cloud Storage under the bucket base (line 15):
- GET
https://storage.googleapis.com/.../claude-code-releases/latest(line 121)
Returns a version string. - GET
https://storage.googleapis.com/.../claude-code-releases/$version/manifest.json(line 124)
Returns platform checksums. - GET
https://storage.googleapis.com/.../claude-code-releases/$version/$platform/claude(line 145)
Downloads the executable.
No explicit data upload is performed by this script. However, once it runs the downloaded binary (line 170), that binary may perform additional network activity.
5) Permissions & privilege escalation
- This script does not invoke
sudo, does not request root, and writes only under$HOME(lines 16, 118, 141). - It executes an unprivileged install step by running the downloaded binary (line 170).
Whether elevated privileges are needed depends on what the claude install subcommand does:
- If it installs into system directories like /usr/local/bin, it may prompt for sudo internally or instruct the user to do so.
- From this script alone, no privilege escalation is performed.
6) Potential concerns (security notes)
- Executes downloaded code
-
The main risk is line 170: it runs a binary fetched over the network. This is the core “curl | bash” concern, even with checksum verification.
-
Checksum trust model relies on the same origin
- The script downloads both the binary and its checksum manifest from the same bucket (lines 124, 145).
-
If the bucket or serving path is compromised, an attacker could replace both the binary and manifest so the checksum still “verifies”.
-
TARGETdoes not control what binary is downloaded - Even if you pass
stableor a version, the script still downloads whateverlatestpoints to (line 121).TARGETonly affects the behavior of the installer binary once executed (line 170). -
This may be surprising if you expected
TARGETto pin the downloaded version. -
Fallback JSON parsing without
jqis brittle -
The regex parser (function
get_checksum_from_manifest, lines 66–82) can fail on unexpected JSON formatting/ordering. It’s not obviously exploitable by itself given it only extracts a 64-hex checksum, but it is less reliable thanjq. -
ldd /bin/lsinvocation -
On some minimal systems
lddmay behave oddly or be absent; errors are piped and grepped (line 109). It’s not a direct security issue, but it’s an extra moving part. -
No signature verification / provenance
- There is no GPG/sigstore/cosign verification. Only a checksum from a remote manifest.
7) Verdict
This is a typical bootstrap installer: detect platform → download manifest → download binary → verify SHA-256 → execute the binary’s installer. There are no obvious malicious behaviors in the shell script itself, and it doesn’t use sudo directly.
The main red flag is inherent to the pattern: it executes a network-downloaded binary, and the checksum is fetched from the same remote location as the binary, so it’s not a strong independent authenticity guarantee. The actual security posture ultimately depends on the integrity and behavior of the downloaded claude binary’s install subcommand.
View raw script
#!/bin/bash
set -e
# Parse command line arguments
TARGET="$1" # Optional target parameter
# Validate target if provided
if [[ -n "$TARGET" ]] && [[ ! "$TARGET" =~ ^(stable|latest|[0-9]+\.[0-9]+\.[0-9]+(-[^[:space:]]+)?)$ ]]; then
echo "Usage: $0 [stable|latest|VERSION]" >&2
exit 1
fi
GCS_BUCKET="https://storage.googleapis.com/claude-code-dist-86c565f3-f756-42ad-8dfa-d59b1c096819/claude-code-releases"
DOWNLOAD_DIR="$HOME/.claude/downloads"
# Check for required dependencies
DOWNLOADER=""
if command -v curl >/dev/null 2>&1; then
DOWNLOADER="curl"
elif command -v wget >/dev/null 2>&1; then
DOWNLOADER="wget"
else
echo "Either curl or wget is required but neither is installed" >&2
exit 1
fi
# Check if jq is available (optional)
HAS_JQ=false
if command -v jq >/dev/null 2>&1; then
HAS_JQ=true
fi
# Download function that works with both curl and wget
download_file() {
local url="$1"
local output="$2"
if [ "$DOWNLOADER" = "curl" ]; then
if [ -n "$output" ]; then
curl -fsSL -o "$output" "$url"
else
curl -fsSL "$url"
fi
elif [ "$DOWNLOADER" = "wget" ]; then
if [ -n "$output" ]; then
wget -q -O "$output" "$url"
else
wget -q -O - "$url"
fi
else
return 1
fi
}
# Simple JSON parser for extracting checksum when jq is not available
get_checksum_from_manifest() {
local json="$1"
local platform="$2"
# Normalize JSON to single line and extract checksum
json=$(echo "$json" | tr -d '\n\r\t' | sed 's/ \+/ /g')
# Extract checksum for platform using bash regex
if [[ $json =~ \"$platform\"[^}]*\"checksum\"[[:space:]]*:[[:space:]]*\"([a-f0-9]{64})\" ]]; then
echo "${BASH_REMATCH[1]}"
return 0
fi
return 1
}
# Detect platform
case "$(uname -s)" in
Darwin) os="darwin" ;;
Linux) os="linux" ;;
*) echo "Windows is not supported" >&2; exit 1 ;;
esac
case "$(uname -m)" in
x86_64|amd64) arch="x64" ;;
arm64|aarch64) arch="arm64" ;;
*) echo "Unsupported architecture: $(uname -m)" >&2; exit 1 ;;
esac
# Detect Rosetta 2 on macOS: if the shell is running as x64 under Rosetta on an ARM Mac,
# download the native arm64 binary instead of the x64 one
if [ "$os" = "darwin" ] && [ "$arch" = "x64" ]; then
if [ "$(sysctl -n sysctl.proc_translated 2>/dev/null)" = "1" ]; then
arch="arm64"
fi
fi
# Check for musl on Linux and adjust platform accordingly
if [ "$os" = "linux" ]; then
if [ -f /lib/libc.musl-x86_64.so.1 ] || [ -f /lib/libc.musl-aarch64.so.1 ] || ldd /bin/ls 2>&1 | grep -q musl; then
platform="linux-${arch}-musl"
else
platform="linux-${arch}"
fi
else
platform="${os}-${arch}"
fi
mkdir -p "$DOWNLOAD_DIR"
# Always download latest version (which has the most up-to-date installer)
version=$(download_file "$GCS_BUCKET/latest")
# Download manifest and extract checksum
manifest_json=$(download_file "$GCS_BUCKET/$version/manifest.json")
# Use jq if available, otherwise fall back to pure bash parsing
if [ "$HAS_JQ" = true ]; then
checksum=$(echo "$manifest_json" | jq -r ".platforms[\"$platform\"].checksum // empty")
else
checksum=$(get_checksum_from_manifest "$manifest_json" "$platform")
fi
# Validate checksum format (SHA256 = 64 hex characters)
if [ -z "$checksum" ] || [[ ! "$checksum" =~ ^[a-f0-9]{64}$ ]]; then
echo "Platform $platform not found in manifest" >&2
exit 1
fi
# Download and verify
binary_path="$DOWNLOAD_DIR/claude-$version-$platform"
if ! download_file "$GCS_BUCKET/$version/$platform/claude" "$binary_path"; then
echo "Download failed" >&2
rm -f "$binary_path"
exit 1
fi
# Pick the right checksum tool
if [ "$os" = "darwin" ]; then
actual=$(shasum -a 256 "$binary_path" | cut -d' ' -f1)
else
actual=$(sha256sum "$binary_path" | cut -d' ' -f1)
fi
if [ "$actual" != "$checksum" ]; then
echo "Checksum verification failed" >&2
rm -f "$binary_path"
exit 1
fi
chmod +x "$binary_path"
# Run claude install to set up launcher and shell integration
echo "Setting up Claude Code..."
"$binary_path" install ${TARGET:+"$TARGET"}
# Clean up downloaded file
rm -f "$binary_path"
echo ""
echo "â
Installation complete!"
echo ""