Claude Code runtime package
The Claude package (runtime-packages/claude: runtime.toml and detect.toml,
nothing else) is the only source of the Claude Code runtime: the daemon compiles
no Claude in. It is the package form of the built-in Claude runtime that
releases up to 0.34.x compiled in. Its compatibility lock and the screens captured from a
real Claude Code are in compat/claude/. See runtime
packages for the generic commands and the Codex
package for the package this one mirrors.
Status: official package in the release catalog; no built-in
Section titled “Status: official package in the release catalog; no built-in”claude is a reserved runtime id. A package serves it only with official trust:
a --catalog install whose signed catalog entry binds the package id, runtime
id and digest, verified against the host’s catalog trust anchor (see runtime
packages). An install trusted by --sha256 is refused
with package_runtime_not_claimable. Since release 0.34.2 every Linux daemon
archive ships the package as runtime/packages/claude.tar.zst with a signed
runtime/runtime-catalog.json whose signer matches the shipped trust anchor;
installing official packages
has the procedure. The Claude hook handler (claude-hook-v1) and its reporter
template stay compiled into core; the descriptor and detection manifest come
only from the package.
The release catalog lists the package for the Linux x86_64 glibc and musl targets, and for Apple Silicon (aarch64-apple-darwin).
On a host without the package nothing serves claude:
pohunek session new --agent claudefails withagent_profile_not_found, andpohunek integration install --agent claudewithruntime_not_installed; both hints namepohunek plugin install runtime/packages/claude.tar.zst --catalog runtime/runtime-catalog.json(run from the extracted release archive). Install the package first, then the hooks withpohunek integration install --agent claude; the package takes the native session id from them.pohunek integration status|doctor|uninstall --agent claudestill work: the core-owned Claude hook handler keeps the config home of the earlier built-in (CLAUDE_CONFIG_DIR, else~/.claude), so hooks pohunek installed there are reported and removed (only pohunek-owned entries) without the package.--profile <name>and--all-profilesreach the homes of host profiles overclaudetoo, each through the profile’s own[env](CLAUDE_CONFIG_DIR), although such a profile does not resolve without the package. A bareintegration statusordoctorwithout--agentcovers only served runtimes, so name--agent claudeto see leftover Claude hooks.- A host profile with
base = "claude"and nopackage/digestkeys fails withruntime_not_installed; its hint names the install and thenpohunek plugin profile migrate <name>, which pins the profile to the installed package. - A
claudein the foreground of a shell session, or started outside pohunek, is not attributed to Claude Code: process recognition and transcript observation read the package’sdetect.tomland[transcripts]table. - A session the built-in Claude of an earlier release launched is refused on
resume, fork and every interaction (input, attach, resize, rename, metadata)
with
runtime_builtin_removed, whose hint names the install; only stop and remove still work. With the package installed, the package adopts the session, live or stopped, and resume and fork run through it when it honors the session’s recovery contract (same native reference kind, hook-reported reference; fork also needs fork support). A session whose contract the package cannot honor answersruntime_served_by_package, and no command rebinds the session to another runtime source.pohunekd upgrade-preflightreports an adoptable session asadoptable, and a session that no package adopts aswould_lose_recovery(runtime_source_not_served); without the package its detail says every interaction is refused until the package is installed. - A package that declares
claude-hook-v1and Claude’s[config_home]under another runtime id reaches the same~/.claude. Requests that name no runtime attribute that home to neither runtime (integration_home_ambiguous_owner) and a bare install writes nothing; name the runtime with--agent. - A bare
pohunek integration installon a host without the package fails withintegration_no_managed_runtime; its hint names the package install and thenpohunek integration install --agent claude. - A shell session hosting
claudestays interactive without the package: input, attach and resize act on the shell, so a Claude identity its hooks report does not need to resolve.
What the package declares
Section titled “What the package declares”The package is version 1.0.1. Every descriptor fact below equals the
built-in Claude descriptor release 0.34.2 shipped (package version 1.0.0),
except the version probe, the transcript tree ([transcripts]) and the input
readiness ([input] ready), which that descriptor does not declare. The
detection manifest is the v0.34.2 built-in one
plus the rules of #613 (below). The
package tests
compare the package with golden copies of that descriptor and its detection
manifest in compat/claude/builtin-v0.34/ (runtime.toml, detect.toml):
the manifest may differ only by the enumerated #613 changes, and only the
enumerated captured frames may read differently.
- Launch:
claudewith the first prompt as a trailing positional argument. Claude’s Ink input takes text without bracketed paste; the submit key is a separate Enter written 150 ms later, and a submit delay the daemon is configured with for this runtime replaces the 150 ms (submit_delay_configurable = true). - Readiness:
ready = "screen_idle". Claude enables bracketed paste before its prompt box accepts a paste, and pastes sent until the box renders are dropped (probe: a paste 0, 200 or 500 ms after theESC[?2004henable was lost, a paste right after the first❯render was accepted in 6 of 6 runs). Only the rendered prompt box, thelive_prompt_boxrule of the detection manifest, starts the initial input. A session whose prompt box does not render within the host’sinitial_startup_grace_msfailssession.newwithsession_input_not_readyand is rolled back; the initial input is not written. Daemons of release 0.34.x reject a descriptor that carriesready. A host profile whose override manifest has no idle rule on a screen region (such aslive_prompt_box) launches with bracketed-paste readiness instead, andpohunek doctorwarns about it. - Version:
version_probe = { parser = "semver-line-v1", args = ["--version"], min = "2.1.289", below = "3.0.0", line = "{version} (Claude Code)" }.claude --versionprints2.1.289 (Claude Code)on standard output; any other wording, a pre-release or trailing text is unsupported. The lower bound is the one release that was verified; the upper bound is the next major release, as for the other packages, because hosts update Claude Code independently of pohunek. Claude Code ships patch releases almost daily, so only 2.1.289 was exercised; later 2.x releases are accepted by policy, not verified. Movingminis a lock and descriptor change made together after a real-Claude run of the new release. - Native reference:
strategy = "hook"; resume isclaude --resume <id>; fork isclaude --resume <id> --fork-session. - Integration: handler
claude-hook-v1with hook schemaidentity-subagent-v1. The package names the compiled handler and schema; the hook reporter scripts stay core-owned, so the archive carries no asset. - Config home:
CLAUDE_CONFIG_DIR, else~/.claude. - Detection (
detect.toml): process matchers are the kernel nameclaudeorclaude.exe, a command line whose first argument isclaudeorclaude.exe(with or without a directory), or a command line whose first argument isnodeorbunfollowed by flag options (-…, no separate values) and a script namedclaude(also.js,.mjs,.cjs) or lying in the@anthropic-ai/claude-codepackage. A command that only names a Claude executable as a later argument (ls -l …/claude.exe,tail -f /work/claude) does not match. Rules read the screen: a status line above the prompt box (✶ Billowing… (3s · ↓ 2 tokens)) or the footeresc to interruptbelow it meansworkingand outranks the visible prompt box; a visible prompt box or a completed-turn line (✻ Brewed for 7s · done 3:34 PM) meansidle; the tool approval dialog (Do you want to proceed?with its options), the selection forms (Enter to select · ↑/↓ to navigate · Esc to cancel, also wrapped), the first-run theme picker, API-key approval and login method list, and the folder trust dialog meanblocked. A title that starts with◐or◑(or a braille spinner) meansworking, a title that starts with✳meansidle.
First-run state of a Claude home
Section titled “First-run state of a Claude home”A fresh CLAUDE_CONFIG_DIR shows first-run screens. The state lives in two
files inside that directory, which a test seeds deliberately and which the
capture also records from a fresh home:
settings.json:{"theme": "dark"}ends the theme picker..claude.json:hasCompletedOnboarding: trueends the welcome flow;customApiKeyResponses.approvedlists the last 20 characters of an API key the owner approved (the key inANTHROPIC_API_KEYotherwise promptsDetected a custom API key, whose default answerNoleads to the login screen);projects.<cwd>.hasTrustDialogAccepted: trueends the folder trust dialog.
A fresh home makes Claude’s startup preflight send an unauthenticated HEAD /api/hello to the configured ANTHROPIC_BASE_URL and to api.anthropic.com,
and Claude exits with Unable to connect to Anthropic services when
api.anthropic.com is unreachable (observed in a network namespace with only
loopback). With a seeded home Claude still sends the HEAD /api/hello to the
base URL (the stub answers 404) but keeps running without network access, so
every test except the first-run capture runs without egress.
Verified against a real Claude Code 2.1.289
Section titled “Verified against a real Claude Code 2.1.289”The screens in compat/claude/screens/ (named screens at 100 columns, widths 20
to 200 in widths/, each with the OSC title Claude had set in a .title file)
were captured from a real interactive Claude Code in a PTY driven by a real
daemon (capture_the_real_claude_first_run_screens and
capture_the_real_claude_screens in crates/cli/tests/claude_package.rs) with a
throwaway HOME and CLAUDE_CONFIG_DIR, a dummy API key
(pohunek-stub-key-…), a loopback Messages-API stub as the model
(crates/cli/tests/support/messages_stub.rs) and no real credential. The first-run
capture needs egress for the preflight above; the seeded captures ran in a
network namespace with only loopback. They cover the theme picker, the API-key
approval, the login method list, the folder trust dialog, the idle screen, a turn
held before its first output, a streaming reply, the screen after a turn, a Bash
tool approval and a multiple-choice question. The binary was the linux-x64
native executable of 2.1.289 from the distribution package, byte-identical
(SHA-256 in the lock) to the one the npm package installs; the real-Claude tests
also ran against the npm install. The darwin-arm64 executable of the same release is pinned by its own SHA-256 and runs in the aarch64-apple-darwin release-evidence row; the screen captures were not repeated on macOS. crates/cli/tests/claude_package.rs classifies every
captured screen through the package manifest.
- The idle, working and blocked states are read from the screen and the title.
Claude keeps its idle title (
✳ Claude Code) while an approval or question dialog is open, and the screen rules (blocked, priority 840 and above) win over it. While a turn runs the title alternates◐ Claude Codeand◑ Claude Codeevery 960 ms (the title component of the 2.1.289 binary). - Claude runs a hook command as
/bin/sh -c <command>. Where/bin/shis dash (Debian, Ubuntu, so the hosted CI runner) the shell stays between Claude and the reporter script, so the script’s$PPIDis that shell and the worker rejected the SessionStart report: no native reference, no resume. The managed Claude scripts now propose the parent of a wrapper shell as the reporting process (theagent-pidblock, which reads the untruncated command line withps -wwand compares the script path quoted as the installer quotes it, so long paths and paths with an apostrophe match; tests incrates/daemon/src/integration/hook_pid_tests.rs), and the real-Claude tests were also run with dash as/bin/sh. The Codex scripts keep$PPID. - The hook runs from the launched process: the SessionStart report names the
launch process as the reporter, so the conversation id becomes the session’s
native reference and names Claude’s transcript file
(
projects/<folder>/<id>.jsonl). Unlike Codex there is no nested reporter. - Resume starts
claude --resume <id>and fork startsclaude --resume <id> --fork-session; both were checked on the process command line of a real run. - A fork is a new conversation: Claude fires SessionStart for it (
source: fork) with the fork’s own conversation id. The forked session holds no reference until its launch process reports one, so that id becomes the fork’s native reference while the source keeps its own, and resuming the fork startsclaude --resume <fork id>without--fork-session(a_real_claude_resumes_and_forks_with_the_descriptor_arguments). - A subagent the model starts through the
Tasktool is reported by the SubagentStart and SubagentStop hooks (identity-subagent-v1) as a Claude subagent that runs and then completes. - Claude Code 2.1.295 fires SubagentStart again with the same
agent_idwhen a finished background agent is resumed withSendMessageor wakes itself after its own background shell ends, and a SubagentStop at the end of that run; the worker reopens the terminal record asrunningand completes it again. About 1 to 3 seconds after every main-agent Stop, Claude fires a SubagentStop with a freshagent_id, an emptyagent_typeand no preceding start for its prompt-suggestion helper;identity-subagent-v1admits a Claude stop only after a recorded start, so the helper adds no subagent row (evidence in #883). - The main-agent Stop input carries
background_tasks, the backgrounded tasks still running or pending (Claude Code 2.1.296). A background subagent’s task id is itsagent_id; teammates, synchronous children and workflow agents are not listed by agent id. The installer registers two Stop hooks insettings.json: the notify hook (turn_completed) and the state hook (stop), which sends each task’sid,typeandstatusas asubagent_snapshotclaim to the worker, with three optional fields sanitized in the hook:label(fromdescription, for every type; control and format characters removed, whitespace collapsed, cut to the configured byte limit),program(the basename of the command word, forshelltasks only;NAME=valueassignments,cd <dir> &&andenvoptions are skipped, and a name that does not match[A-Za-z0-9][A-Za-z0-9._+-]*is omitted) andagent_type(forsubagenttasks only; a value containing a control, format, line or paragraph separator or surrogate character is dropped from its task). A command longer than 64 KiB gets noprogram, because shell tokenizing grows superlinearly with token length (about 32 ms at 64 KiB, 6 s at 1 MiB) and a cut command word could name another program. The command and its arguments are never forwarded. The label and program limits come fromPOHUNEK_BACKGROUND_LABEL_MAX_BYTESandPOHUNEK_BACKGROUND_PROGRAM_MAX_BYTES, which the worker injects; a missing or invalid value forwards no label or program. An invalid optional field is dropped from its task only, while a missing or invalidid,typeorstatusvoids the list. The hook reads at most 1 MiB and sends nothing for a Stop with a non-emptyagent_idor a missing, invalid or over-long list. Claude builds the list before the hook reads its clock and often still lists a fast background agent whose own SubagentStop already arrived, so the worker never reopens a listed row: it completes background agents of the same Claude process that an earlier list named and this one omits, and leaves listed rows to their own start and stop (see sessions; evidence in #883). - The
sessionaction (ClaudeSessionStart) forwards the hook inputsourceassession_sourcein its identity report only when it isstartup,resume,clearorcompact; any other value, or none, omits the key.forkis not forwarded: a forked session is a new process and must not clear the snapshot of the session it was forked from. ASessionStartfrom another process clears the snapshot;startupandresumeof the same process clear it too, whileclearandcompactkeep it. - The
releaseaction (ClaudeSessionEnd) forwards the hook inputreasonasrelease_reasonin itsidentity_releaserequest only when it isclear,resume,logout,prompt_input_exitorother; any other value omits the key. The snapshot belongs to the reporting Claude process: any process in the managed PTY tree may report, the latest turn end wins, and the owner’s release with a reason other thanclearclears it. A/clearkeeps it, because Claude follows that release with aSessionStartof the same process. - A Claude state hook action (
session,release,subagent-start,subagent-stop,stop) that fails to deliver its report, including astopthat sends no snapshot, still exits 0 and appends one payload-free line per reason (providerclaude, the action and a reason code) to the hook failure tracepohunek-hooks.jsonlin the pohunek log directory (see the debug-session-runtime runbook). claude --versionprints2.1.289 (Claude Code); the inventory shows the package’s probe accepted the real release.- Environment that keeps a real Claude away from host state and services:
CLAUDE_CONFIG_DIR,ANTHROPIC_BASE_URL,ANTHROPIC_API_KEY(dummy),ANTHROPIC_MODEL,DISABLE_AUTOUPDATER,DISABLE_UPDATES,DISABLE_TELEMETRY,DISABLE_ERROR_REPORTING,CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFICandCLAUDE_CODE_DISABLE_OFFICIAL_MARKETPLACE_AUTOINSTALL(without the last one a first interactive launch registers the official plugin marketplace).
Detection beyond the built-in manifest
Section titled “Detection beyond the built-in manifest”The package manifest adds these rules to the v0.34.2 built-in one (#613). Each is backed by screens or process forms captured from 2.1.289 and by a package test that fails without the rule.
- Running-turn title.
osc_title_workingalso takes◐and◑, the glyphs 2.1.289 alternates before its title while a turn runs (the_title_reads_idle_for_the_asterisk_and_working_for_the_turn_glyphs). The title outranks the screen whenever it changes. - Running-turn footer. A streaming reply hides the status line, and the
daemon evaluates the title only when it changes, so a reply re-rendered
between two title changes would read idle through the visible prompt box.
The footer below the prompt box reads
esc to interruptwhile a turn runs (? for shortcutswhile Claude waits);live_turn_footerreads it asworkingat the priority and with the dialog exclusions of the status-line rule (a_streaming_reply_reads_working_from_its_title_and_turn_footer,a_real_claude_reply_held_mid_stream_reads_working). - Wrapped selection forms.
live_blocked_formseparates the words of its hints by any whitespace, so the question form readsblockedat 30 and 40 columns, where its footer wraps (Enter to select · ↑/↓ to/navigate · Esc to cancel). - First-run and trust dialogs.
theme_picker(Choose the text style…with the dark and light modes),login_method_list(Select login methodwith the subscription and Console options) andfolder_trust_dialog(Yes, I trust this folder,No, exit,Enter to confirm) readblocked; the trust dialog at every captured width, its heading scrolled away at 20 columns. These are the dialogs Claude waits on before it opens its prompt. The rules read only the text below the last horizontal rule on screen, where Claude draws these dialogs; a transcript that quotes them sits above the prompt box’s rules, so the screen keeps reading idle or working (quoted_dialog_text_in_the_transcript_is_not_a_dialog). - Completed-turn line.
completed_turn_statustakes the· done <time>2.1.289 appends (✻ Brewed for 7s · done 3:34 PM; the clock follows the host locale). - Process forms. The npm package’s executable
bin/claude.exeruns as kernel nameclaude.exewith a command line ending inclaude.exe; both matchers accept it, and the real launch test requires the launched process to match with either install. Throughnodeorbunthe script must be namedclaudeor lie in the@anthropic-ai/claude-codepackage, so a script that merely carries the word (node /opt/claude-companion.mjs) is not Claude, and a node option with a separate value (node -r ./preload.js …/claude) hides the script that follows it. A launcher that only names the package (npx @anthropic-ai/claude-code,bun x @anthropic-ai/claude-code) is not matched itself; theclaude.exeit starts is (the_process_matchers_accept_the_observed_claude_process_forms).
Limits that remain:
- At 30 columns and fewer the footer is cut before
esc to interrupt(⏸ manual mode on · esc to…), so a streaming reply re-rendered there reads idle until the next title change. - Under a terminal multiplexer (
TMUX,STYorZELLIJin Claude’s environment) 2.1.289 keeps the static✳title during a turn (itstengu_static_title_under_muxswitch, on by default); the screen rules are then the only working evidence. Not exercised in a real run. - At 20 and 30 columns the completed-turn line wraps before
· done, so the prompt box alone reads idle there. - The theme picker and the login method list were captured at 100 columns only; the rules separate their words by any whitespace for narrower terminals, which no capture confirms.
Not verified
Section titled “Not verified”Releases other than 2.1.289 (the range above the lock is a policy, not a
verification), the native installer’s ~/.local/bin/claude launcher and the
Homebrew and apt installs (the npm install and the distribution package ship the
same binary), macOS and Windows, a real login or a real model, plan mode,
compaction, MCP and other dialogs, the PermissionRequest hook, terminals
narrower than 20 columns, and a hosted CI run of the claude-package job. The
first-run capture needs egress; the absence of egress in every other run is
established by the network namespace, not by Claude’s own switches.
How the package is kept honest
Section titled “How the package is kept honest”crates/cli/tests/claude_package.rsalways runs: it builds the directory twice and requires identical bytes, parses it with the daemon’s install parser, compares every descriptor fact and the whole manifest with the v0.34.2 built-in Claude goldens incompat/claude/builtin-v0.34/(as parsed definitions, as TOML data, and by feeding both manifests every captured frame, title and synthetic dialog; the manifest differences and the frames they reclassify are enumerated, each with what the package reads), checks resume and fork argv, requires the supported range to equalcompat/claude/compatibility-lock.json, reads the version banner, runs the title, screen and process rules, and asserts the--sha256install refusal above.- The daemon-backed tests install the built archive through
pohunek plugin install --catalog, the way a host with a catalog trust anchor does: the test process generates a throwaway signing key, gives the daemon an anchor that trusts it (crates/test-support/src/catalog.rs, featurecatalog), and signs a catalog entry binding the package id, the runtime idclaudeand the archive digest. The key never leaves the process and no real key or catalog is involved. The package then servesclaudewith official trust, profiles carry itspackage/digestpin, and a launch runs the package’s version probe on the executable the profile names. An always-running test drives that probe with throwawayclaudescripts: the older release, the next major, a pre-release, a different banner wording and unreadable output are refused withagent_runtime_unsupported, and the supported banner and a later 2.x release launch. - Always-running clean-host tests start from a daemon without the package and
a fake
claudeof the locked release onPATH: the bare runtime, its integration and an unpinnedbase = "claude"profile are refused with the install hint (and the migration for the profile), and after the catalog install the bare runtime launches pinned to the package and the migrated profile launches. Others install the hooks into the hermetic~/.claude(and into two host profiles’ ownCLAUDE_CONFIG_DIR) through the package, uninstall the package, and check thatintegration status,doctoranduninstall --agent claude(with--profileand--all-profiles) still report and remove them whileintegration installis refused, that a malformed profile is refused instead of being read with an empty environment, and that the assistant answersno_capable_agenton such a host. One more installs the hooks and checks that the installed reporter scripts equal the recorded renderings (crates/daemon/src/integration/reporter_golden/claude-*.sh) byte for byte. - The test daemon observes the host through the test-host process view
(
crates/daemon/src/procwatch/readable_host.rs, viaSessionRegistry::new_production_with_inspector), so a same-user process of the test host that started after a worker and hides its environment (another parallel test’s fresh worker, a non-dumpable helper) does not make a session removal refuse withruntime_supervision_ambiguous. The hosted runner hit that refusal once ina_real_claude_resumes_and_forks_with_the_descriptor_arguments;a_session_removal_ignores_an_unreadable_process_of_the_test_hostreproduces it with a non-dumpable helper. The production daemon keeps the fail-closed host view. - The fixture owns a process guard (
crates/cli/tests/support/process_guard.rs): on success and on unwind it kills every process whose executable, working directory or environment value lies below the fixture’s root before the directories are removed. Two tests pin the guard. #[ignore]d tests drive a realclaudethrough the installed package with a throwaway Claude home and the loopback Messages stub: the banner against the probe template, launch and detection (input framing, the held turn, the process matchers on the real process; the stub sends no text until its start gate opens, so the working screen is stable), a reply held mid-stream (it must not read idle for longer than two title changes), the SessionStart reference and its transcript, resume and fork argv, the subagent hooks (the same subagent must reachcompletedwhile the parent runtime is live), and the approval dialog. The subagent tracking of #883 has three more:a_resumed_real_claude_background_agent_keeps_one_row_through_both_runs(a background agent resumed withSendMessagekeeps one row,runningduring each run andcompletedafter it),a_real_claude_background_agent_without_its_stop_is_settled_by_the_main_stop(pohunek’s SubagentStop hook is removed; an earlier main Stop listed the running agent, and the first main Stop whose list omits the finished agent completes it, logged assnapshot_absent) andreal_claude_prompt_suggestion_stops_create_no_subagent_row(the prompt-suggestion helper’s stops create no row). The helper test setsCLAUDE_CODE_ENABLE_PROMPT_SUGGESTION=1in the session profile, because without it Claude reads the helper’s enablement from a remote feature flag that a hermetic run never fetches. Hooks come frompohunek integration install --agent claude --profile …, which resolves the profile’sCLAUDE_CONFIG_DIRso the real home is never touched. Run them withPOHUNEK_CLAUDE_E2E=1 cargo test -p pohunek-cli --test claude_package -- --include-ignored --test-threads 1; without the variable and aclaudeonPATHthey fail by name instead of skipping. Never point a manual run at~/.claude, and do not run it from inside a Claude Code session without removing everyANTHROPIC_*andCLAUDE*variable: the fixture gives Claude its own environment, but the surrounding shell must not carry a real key into the test process.- To prove the absence of egress, run the test binary in a network namespace
with only loopback (
unshare -rn --pid --fork --mount-proc, thenip link set lo up). The daemon trusts an executable only when root or the effective user owns it, so under the namespace’s user mapping put a user-owned copy of theclaudebinary first onPATH; the pid namespace keeps host processes out of the daemon’s runtime-release scan. - Refreshing the screens:
POHUNEK_CLAUDE_E2E=1 POHUNEK_CLAUDE_CAPTURE_DIR=<abs dir> cargo test -p pohunek-cli --test claude_package -- --ignored capture_the_real_clauderecords the first-run screens (needs egress) and the seeded screens (run it without egress); without the directory variable the capture tests do nothing. - The
claude-packageCI job installs exactly the locked release from npm (@anthropic-ai/claude-code@2.1.289, with install scripts enabled because the postinstall links the native binary;--ignore-scriptsleaves a launcher that refuses to run), compares the native binary with the lock’s SHA-256, builds the archive withcargo xtask package buildand runs that file.
Update procedure for a new Claude Code release: install it, rerun the
real-Claude tests, recapture the screens if the interface changed, move the lock
(release, SHA-256) and the descriptor’s min/below together, and bump the
package version.