Skip to content

feat(e2e): claude-code smoke with Zstd + tar.gz + archive verify#12

Merged
robertsLando merged 9 commits intomainfrom
e2e/claude-code-smoke-zstd
Apr 23, 2026
Merged

feat(e2e): claude-code smoke with Zstd + tar.gz + archive verify#12
robertsLando merged 9 commits intomainfrom
e2e/claude-code-smoke-zstd

Conversation

@robertsLando
Copy link
Copy Markdown
Member

Summary

  • Adds Zstd to the compress-node input enum in packages/core/src/inputs.ts (+ regenerated action.yml, packages/build/action.yml, docs/inputs.md via yarn gen, rebundled dist). Zstd requires Node.js ≥ 22.15 on the build host — .node-version already pins 22.22.
  • New claude-code-smoke e2e job that pulls @anthropic-ai/claude-code from npm, builds a native binary per runner with SEA + compress-node: Zstd + compress: tar.gz + sha256 checksum, then runs claude --version on the binary and on the tar-extracted copy.
  • Matrix covers real cross-OS + cross-arch: ubuntu-latest/linux-x64, ubuntu-24.04-arm/linux-arm64, macos-latest/macos-arm64, windows-latest/win-x64. SEA mode chosen because claude-code is ESM and standard pkg can't bytecode-compile ESM.
  • Final verification step recomputes the archive's sha256 locally and diffs against the emitted sidecar (catches post-build tampering), validates the .tar.gz extension, extracts the tarball, and re-runs --version on the extracted binary.

Test plan

  • yarn test — 225/225 pass, including new parseInputs accepts Zstd compress-node case
  • yarn lint — clean
  • yarn gen + yarn build — committed outputs match source (codegen-drift job will re-verify on CI)
  • claude-code-smoke job green on all 4 runners (ubuntu x64, ubuntu arm64, macos arm64, windows x64)
  • Existing e2e jobs (tiny-cjs, matrix-fanout, multi-target-linux, windows-metadata) still green

🤖 Generated with Claude Code

- Add `Zstd` to the compress-node enum (core/inputs.ts), regenerate
  action.yml / packages/build/action.yml / docs/inputs.md, rebundle
  dist. Zstd requires Node.js >= 22.15 on the build host.
- New `claude-code-smoke` e2e job: installs @anthropic-ai/claude-code
  from npm, builds via SEA mode with compress-node=Zstd + tar.gz +
  sha256 on ubuntu-x64/arm64, macos-arm64, windows-x64. Runs the
  binary with `claude --version`, then recomputes the sha256 against
  the sidecar and extracts the tarball to re-verify --version on the
  archived copy.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings April 23, 2026 08:50
- @anthropic-ai/claude-code >= 2.1.117 ships as a thin wrapper that
  delegates to a native binary via optionalDependencies; pkg can't
  bundle that. Pin 2.1.114, the last release whose `bin.claude`
  points at a real `cli.js`.
- pkg 6.17 is the first yao-pkg/pkg to accept `--compress Zstd`; the
  action's default pkg-version (~6.16.0) rejected it with "Invalid
  compression algorithm Zstd". Override to ~6.18.0 for this job.
- Resolve `pkg.bin.claude` explicitly (instead of Object.values()[0])
  so a future per-OS alias can't steer us at the wrong entrypoint.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR extends the action’s compress-node input to support Zstd compression and adds a new real-world E2E smoke workflow job that builds and validates a SEA-packaged @anthropic-ai/claude-code binary across multiple OS/arch runners.

Changes:

  • Add Zstd to compress-node input parsing/types, plus regenerated action.yml, packages/build/action.yml, docs, and bundled dist/ artifacts.
  • Add unit test coverage ensuring parseInputs accepts canonical Zstd (and still rejects lowercase zstd).
  • Add claude-code-smoke E2E job that builds a SEA binary with compress-node: Zstd + tar.gz, runs --version, then verifies archive integrity and checksum sidecar.

Reviewed changes

Copilot reviewed 6 out of 8 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
packages/core/src/inputs.ts Adds Zstd to input description, type union, and enum parsing.
packages/core/test/unit/inputs.test.ts Adds unit test ensuring Zstd is accepted and validates case-sensitivity behavior.
.github/workflows/e2e.yml Adds claude-code-smoke job to build/run/verify an archived SEA binary across OS/arch matrix.
action.yml Regenerated input docs to include Zstd in compress-node description.
packages/build/action.yml Regenerated input docs to include Zstd in compress-node description.
docs/inputs.md Regenerated inputs documentation for compress-node: Zstd.
packages/build/dist/index.mjs Rebundled dist reflecting new compress-node enum value and description.
packages/windows-metadata/dist/index.mjs Rebundled dist reflecting updated compress-node description.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread .github/workflows/e2e.yml Outdated
robertsLando and others added 7 commits April 23, 2026 11:00
- 2.1.113 already switched @anthropic-ai/claude-code from a pure-JS
  cli.js entrypoint to a native-binary wrapper (bin/claude.exe). My
  earlier pin of 2.1.114 landed inside the new layout, so pkg was
  compiling a shell-script placeholder that errors at runtime. 2.1.112
  is the last pure-JS release — verified locally with SEA+Zstd: builds
  in ~20s, binary runs and prints "2.1.112 (Claude Code)" on --version.
- Previous `--version` step captured output via `out=$(... 2>&1)` under
  `set -e`, which aborted before echoing when the binary exited non-
  zero (as it did on every runner). Suspend `set -e` around the capture
  so the logs actually show what the binary printed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Surfaced by the new claude-code-smoke e2e:

- macOS: the macos-latest runner's bsdtar rejects `--mtime` outright
  with "Option --mtime is not supported", even though upstream
  libarchive accepts it. The action was always passing --mtime; tiny-
  cjs never hit this because its macOS matrix entry uses zip. Keep
  --mtime on GNU tar (linux) only; rely on the utimes pre-pin for
  bsdtar paths (archive() is single-file, so the header inherits that
  mtime). Test updated to assert --mtime presence only on linux.

- Windows: git-bash's `sha256sum` escapes the output line with a
  leading backslash when the filename contains backslashes (Windows
  paths), per GNU coreutils convention. The e2e's verify step was
  comparing `\<digest>` against the sidecar's clean hex. Strip the
  leading backslash via sed before parsing.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
GNU tar from git-bash parses `D:\a\...` as a remote host:path target
(colon-separated rsh syntax), producing:
    tar (child): Cannot connect to D: resolve failed

Convert any Windows drive-letter path to the POSIX form (`/d/a/...`)
via cygpath before invoking tar. No-op on Linux/macOS — falls back to
echo when cygpath isn't on PATH. --version + sha256 sidecar check
already passed on Windows in the previous run; this unblocks the
tar-round-trip tail.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Review follow-ups on #12:

- Guard the pin: assert the resolved bin entry is a JS file (.js/.mjs/
  .cjs) before patching it into the fixture package.json. If the
  @anthropic-ai/claude-code pin ever drifts past 2.1.112 into the
  native-binary wrapper layout (bin → bin/claude.exe shell-script
  placeholder), fail loudly rather than bundling garbage.
- Replace the ad-hoc `${fix//\\//}` backslash-strip with the same
  cygpath helper used in the verify step, so the fixture path emitted
  to GITHUB_OUTPUT is always POSIX-form on Windows git-bash.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…mpatible

Reverting the $fix cygpath conversion from 3fca6c7. cygpath -u emits
POSIX form (/d/a/...), which GNU tar in git-bash handles correctly
but Node's fs on Windows misparses as drive-relative, producing paths
like D:\d\a\_temp\... and failing with ENOENT. The composite's build
step is Node-based, not shell-tar-based, so it wants the drive-letter-
plus-forward-slash form (D:/a/...) that ${fix//\\//} produces.

cygpath stays in the verify step where tar is the consumer — different
tool, different path convention.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Surfaced by matrix e2e debugging: the orchestrator emitted every pkg
argv twice (once as "[pkg-action] pkg …" in main.ts, once as
"[pkg-action] Invoking: …" from the runner) and was silent through the
finalize stages — no signal for when archive started, when the
checksum was computed, or which shasum files were written.

- Drop the main.ts pre-log; runPkg's "Invoking:" already carries the
  command path, and GH Actions renders the raw `[command]` line too,
  so three copies was overkill.
- Log pkg wall time after runPkg completes.
- Group the per-target finalize loop under a `logger.startGroup` so
  each target collapses into its own fold in the GH Actions UI —
  matters for matrix runs where 4+ targets interleave.
- Add explicit archive/checksum progress lines with sizes + timings
  (`archive → …`, `archived … (17.8 MB, 4.2s)`, `sha256 <digest>
  <file>`) so any downstream verification failure trivially diffs
  against the build log.
- Log each SHASUMS file as it's written, with entry count.
- Include overall wall time in the final "done" line.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The pkg CLI emits ~15–30 lines per build (Walking dependencies, node
download/extract, SEA asset generation, blob injection, strip warnings)
and the GH-Actions exec shim adds `[command]` echo on top. On a multi-
target run all of that interleaved with finalize output is hard to
skim.

Wrap runPkg in startGroup/endGroup with a header that names the target
list, so reviewers see a single collapsible "pkg build (targets=…)"
block followed by the one-line wall-time summary. Same pattern we
already use for the per-target finalize blocks.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@robertsLando robertsLando merged commit 2cb95b8 into main Apr 23, 2026
19 checks passed
robertsLando added a commit that referenced this pull request Apr 23, 2026
Main (PR #12) wired the claude-code-smoke job through the action using
`mode: sea` and `compress-node: Zstd` inputs. Those inputs no longer exist
on this branch, so move both values into the package.json#pkg field that
the fixture-prep step already injects. No behavior change on main; on this
branch the e2e now exercises the pkg-config path the slim input surface
forces users onto.
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