Repository navigation
feat(installer): tag-triggered Windows installer, plus a dry run that publishes nothing - #2
Merged
Merged
Conversation
…t Claude Code Pushing a v* tag now builds a PyInstaller one-file exe on windows-latest, wraps it in an Inno Setup installer, and attaches it to that tag's GitHub release. The installer lands in Program Files, appends itself to the machine PATH (HKLM), and registers an uninstaller that removes exactly that PATH entry. The console entry point is a module function targeting `__main__`, so PyInstaller is aimed at installer/claude_batch_runner_launcher.py. Importing claude_batch_runner.__main__ binds __name__ to that dotted path rather than "__main__", so the module's own guard does not fire and argparse does not run twice; only the launcher is __main__ in the frozen exe. Two things the build refuses to paper over. The workflow fails a tag whose version disagrees with pyproject.toml, and the .iss declares no default AppVersion, so ISCC errors rather than emitting a 0.0.0-dev installer. Inno Setup is installed via choco rather than assumed present on the runner image. Every unit of work is a `claude -p` subprocess, so the tool cannot run a campaign without the Claude Code CLI on PATH. That is not bundled -- Claude Code is separately versioned and self-updating -- so the installer's finish page and the README both name it, give the install command, and say to sign in before first use. Neither claims anything about how that login is provisioned; the runner inherits whatever the CLI already has. The build is not code-signed and the README says so plainly, SmartScreen box included, with `pip install .` offered as the no-installer path.
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.
Summary
Ports the reviewed installer pattern from
keycut(PR #2/#3). Pushing av*tag builds a Windowsinstaller on
windows-latest— PyInstaller one-file exe, wrapped by Inno Setup, installed toProgram Files, appended to the machine PATH, registered in Add/Remove Programs — and attaches it to
that tag's release.
workflow_dispatchruns the identical build and stops short of publishing,leaving the installer as an artifact, so the Windows half can be proven without a public release.
The external requirement, established from source
Claude Code (
claude) must be installed separately and be on PATH. This is not incidental:driver.py:85-96builds["claude", "-p", prompt, "--agent", …]anddriver.py:100runs it viasubprocess.run. Every unit of work is one such process, so with noclaudebinary every unitreturns
FLAGGED. Corroborated atpyproject.toml:23-24andREADME.md:44-50. The installer'sfinish page and the README both say so, with the install command.
Authentication is inherited and never handled here —
driver.py:9-10states this module "neverreads, sets, or forwards credentials of its own", and a grep for
API_KEY|api_key|getenv|requests| urllib|httpacross the package returns no credential handling. The README says only: runclaudeonce and sign in. No mechanism is invented, and nothing about pricing, quota or plan entitlements
appears anywhere.
The install command is documentation-verified, not recalled:
winget install Anthropic.ClaudeCodeon the finish page, with the native installer and
claude --versionin the README, taken fromcode.claude.com/docs/en/setup.The
__main__hazard, resolved by executionThe entry point is
claude_batch_runner.__main__:main, and that module carries its ownif __name__ == "__main__": sys.exit(main())at lines 60-61 — a launcher that re-triggered it wouldrun argparse twice. Proved by running it that importing as a submodule binds
__name__toclaude_batch_runner.__main__, so the guard does not fire:Had it fired, argparse would have exited 2 on the missing
--spec. The launcher docstring records why.Test plan
python -m PyInstaller --onefile --console --name claude-batch-runner …→ build completedist/claude-batch-runner --help→ exit 0;--spec examples/demo-campaign.json --dry-run→exit 0 with a full valid plan, so real logic is reached inside the freeze
PyInstaller-internal. Zero package modules missing → no
--hidden-importpytest -q→ 65 passed, re-run unpiped by the dispatcher.isspure ASCII, no BOM,[Registry]PATH on one line with zero\continuationsLint is pre-existing, not a regression: baseline at
625a44bis identicallyFound 9 errors/3 files would be reformatted, all in files this PR does not touch. The launcher alone is clean.ci.ymlruns pytest only, so no lint job gates this.Unverified until a real Windows run
The entire Windows half: the ISCC compile, the HKLM PATH add/remove Pascal (needs a real
install/uninstall, not just a build), the Windows freeze, elevation/SmartScreen, and the
gh releasecreate-vs-upload branch. Merge this after keycut's dry run has gone green — thesame chain is now in four repos and a flaw found once should be fixed once.
Minor and pre-existing:
--helpprintsusage: claude_batch_runner, not the exe name, fromprog=at
__main__.py:18. Fixing it means touchingsrc/, which is out of scope here.Diff 285 sum (cap 300).