feat: idempotent update, --force-rebuild flag, wrapper version display, deeper smoke tests - #10
Merged
Merged
Conversation
added 4 commits
May 21, 2026 20:23
…y, deeper smoke tests `sclaude update` / `scodex update` and `sclaude version` / `scodex version` had three rough edges. This addresses all three plus adds a smoke test that would have caught the recent Codex config-loading regression. 1. `update` is now idempotent. New `needs_image_rebuild` helper queries the npm registry for the latest published @anthropic-ai/claude-code and @openai/codex versions, compares against what is installed in the current image, and skips the no-cache rebuild when both already match. Running `update` twice in a row no longer pays the full rebuild cost the second time. Any error along the way (no curl, no image, npm unreachable) defaults to "rebuild needed" so a real update is never silently skipped. 2. New `--force-rebuild` flag bypasses the npm version check and forces the no-cache + --pull rebuild. Parsed by parse_args before dispatch so it never leaks through to the inner CLI. 3. `sclaude version` / `scodex version` (and the `--version` aliases) now show "Wrapper version: vX.Y.Z" using `WRAPPER_VERSION`. The line that previously read "Script version: <hash>" is renamed to "Image hash:" — that field has always been the Docker layer hash, never the wrapper version, and the old label was misleading. 4. New T17b/T17c smoke tests exercise `codex exec --help` and `claude --help` and assert no config-load error strings appear. T17 was running `--version` only, which is too shallow to reach the inner CLI's config-init code and missed the recent Codex "Failed to load Cloud requirements" regression.
…arse, tighter test asserts Addresses the six findings from the codex review on PR #10. - self_update_wrapper preserves --force-rebuild across its re-exec so the user's intent survives the wrapper swap (codex review thread on sclaude/scodex:422). - needs_image_rebuild matches full semver including prerelease/build suffixes (e.g. 1.2.3-beta.1) on both the image side and the npm side, so update no longer rebuilds every run when a prerelease is the npm "latest" tag (thread on :439). - New fetch_npm_latest helper parses the npm /latest document via python3 when available and falls back to a precise grep that only matches the first "version" field. The previous greedy sed could attach to a nested key (thread on :445). - When the image is already at latest the wrapper now also prints `<wrapper> update --force-rebuild` as the documented way to refresh base OS / apt state, so the skip path no longer silently regresses the security-refresh behavior of the old unconditional rebuild (thread on :711/741). - T10 now invokes `update --force-rebuild` and asserts the rebuild banner appears in output, so the skip-if-up-to-date path can't silently swallow the test's intent (thread on test_e2e.sh:244). - T17b/T17c regex tightened to specific known config-load error strings instead of the broad `Failed to load .*` that could false-positive on benign output (thread on test_e2e.sh:353).
… cap T17 timeouts Three more issues from re-running `codex exec review` on the harden commit. - `fetch_npm_latest`'s python3 path and no-python grep fallback could both return non-zero (python parse error, or grep finding no match) and trip `set -e` in the caller, which would abort `update` instead of falling back to a rebuild. Both paths now end with `|| true` and the function explicitly returns 0 (codex review thread on sclaude/scodex:457). - The semver regex in `needs_image_rebuild` only allowed one of prerelease OR build metadata via `([-+]…)?`. A valid semver such as `1.2.3-beta.1+build.5` was truncated to `1.2.3-beta.1`, causing endless rebuilds whenever npm reported the full string. Replaced with the full shape `[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z.-]+)? (\+[0-9A-Za-z.-]+)?` on both probes (codex review thread on sclaude/scodex:479). - T17b and T17c inherited the global 600s timeout. A hang in inner-CLI config loading would burn 20 minutes per CI run. Cap their per-test timeout at 120s (configurable via `T17_TIMEOUT_SECONDS`) and restore the original value before T18 (codex review thread on test_e2e.sh:344).
…-force-rebuild Six more findings, all addressed. - `needs_image_rebuild`'s `$ENGINE_CMD run … --version` probes had no timeout. A hang during inner-CLI startup would freeze `update` before it could fall back to rebuilding. New `probe_image_tool_version` helper wraps the probe in `timeout 15` (or `gtimeout` on macOS with coreutils, no-op otherwise) and only looks at the first line of output (so an update banner cannot displace the actual version token). - `verify_image_built` previously verified only `$TOOL_BIN`. Since the image is shared between Claude and Codex, `sclaude update` was passing even when the rebuilt image's Codex install was broken (and vice versa). Now verifies both `claude --version` and `codex --version` and exits non-zero on either failure. - `--force-rebuild` was consumed in parse_args even when the user invoked a non-update command. `sclaude --force-rebuild "do a thing"` silently lost the flag. Added a guard in parse_args that errors out when FORCE_REBUILD is true but WRAPPER_COMMAND is not `update`. - `fetch_npm_latest` no longer has a regex fallback for hosts without python3 — codex worried (correctly) that scanning the first 1KB of the registry JSON could misattribute a nested "version" key. With no parser available, the function returns empty and the caller defaults to rebuild. python3 is already a runtime dependency of the wrapper (used in show_version metadata pretty-print). - Image version regex moved into the new helper, behind `head -1` so the first semver-shaped token is taken only from the first output line, not from any subsequent line.
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.
Why
A handful of UX gaps came up after v2.4.0 shipped:
sclaude update/scodex updatealways paid the full--no-cache --pullrebuild cost, even when running twice in a row with nothing new on npm.sclaude version/scodex versionprinted"Script version: <hash>", which is actually the Docker layer hash — there was no way to see the installed wrapper'sWRAPPER_VERSION.codex --version, which is too shallow to reach the inner CLI's config-init code; the recent "Failed to load Cloud requirements (workspace-managed policies)" regression slipped past CI as a result.Changes
Idempotent
update— newneeds_image_rebuildhelper querieshttps://registry.npmjs.org/{@anthropic-ai/claude-code,@openai/codex}/latest, compares to what is baked into the current image (via<engine> run --rm <image> claude/codex --version), and returns 0 (rebuild) or 1 (skip) accordingly. Any error along the way (no curl, no image, npm unreachable) defaults to rebuild so a real update is never silently skipped.--force-rebuildflag — bypasses the npm check and forces the no-cache + --pull rebuild. Parsed inparse_argsso it never leaks through to the inner CLI.Wrapper version in
versionoutput — addsWrapper version: vX.Y.ZfromWRAPPER_VERSION, and renames the misleading"Script version"line to"Image hash"(that field has always been the Docker layer hash).Deeper smoke tests —
scodex exec --helpmust exit 0 and not printError loading configuration/Failed to load Cloud requirements/Failed to load .* policies.sclaude --help.Testing
pre-commit run --all-files— actionlint, shellcheck, bash -n, zsh -n all green.bash -n sclaude scodex test_e2e.shandzsh -n sclaude scodex test_e2e.shpass.sclaude versionon macOS prints the new format correctly (Wrapper version: vX.Y.Z, Image hash: …).