Skip to content

The frozen binary takes ~19 s to run ANY command, including --version — PyInstaller onefile re-extracts 14 MB on every invocation #349

Description

@hkngln

Measured

Installed v0.5.0-rc3 via install.sh on macOS arm64 (tan-aarch64-apple-darwin, sha256 183b28dc310c1def8b05af1fb7d84eb7ffdd9e445ea2188b2b3ee982ba3f0669), /usr/bin/time -p tan --version, five consecutive runs:

13.25 s
19.35 s
19.35 s
18.58 s
19.74 s

For reference on the same host, same shell:

git --version        0.01 s
python3 -c pass      0.03 s

--version is the cheapest possible command — it does no I/O, no network, no SDK resolution. So this is pure process startup, and every subcommand pays it. There is no warm path: repeat runs are not faster, and the first run was actually the fastest of the five.

Cause

The binary is a PyInstaller onefile bundle. Confirmed:

  • strings on the executable contains pyi-runtime-tmpdir and PyInstaller's PKG-archive error string.
  • While a command is running, a fresh extraction directory exists — /var/folders/dh/…/T/_MEIYC8HRM, 15 entries — and it is gone afterwards.

So each invocation unpacks 14 MB of Python runtime plus shared libraries into a new temp directory, runs, and deletes it. On macOS every extracted .dylib/.so is unsigned once written (the parent's ad-hoc signature does not cover the extracted copies), so the OS inspects them individually on load. That is where the ~19 s goes.

Signature state, for completeness:

$ codesign -dv ~/.local/bin/tan
CodeDirectory v=20400 size=110693 flags=0x2(adhoc) hashes=3453+2 location=embedded

$ spctl -a -vv ~/.local/bin/tan
/Users/…/.local/bin/tan: rejected

$ xattr -l ~/.local/bin/tan
(empty — no com.apple.quarantine, so this is NOT the download-quarantine case)

Why this matters more than it looks

A 19-second --version is not a slow tool, it is an unusable one. Every place tan is invoked repeatedly pays it in series:

  • the VS Code extension, which shells out per keystroke-ish interaction;
  • tan build over a multi-slice plan;
  • CI jobs that call several subcommands (clean-host.yml runs four in a row — a large part of that job's wall-clock is this, not the assertions);
  • anyone exploring the CLI, where --help on a few subcommands is a minute of waiting.

It also silently distorts every timing-based judgement made about the tool so far.

Directions

Roughly in order of effort/benefit — the first is the standard remedy for exactly this:

  1. Ship --onedir instead of --onefile. Extraction happens once at install time rather than on every run; startup drops to normal Python-import cost. Costs: the asset becomes a tarball/zip rather than a single file, and install.sh must unpack it and place a launcher. This is what the size of the win argues for.
  2. Sign and notarize the macOS binaries. Ad-hoc → Developer ID + notarization would let the OS trust the extracted libraries. Helps macOS; does nothing for the re-extraction cost itself, and does not help Linux.
  3. Set a stable pyi-runtime-tmpdir so the extraction directory is reused across runs rather than recreated. Cheapest change, but it caches security-sensitive extracted binaries in a fixed location — wants thinking about before adopting.

Not proposing a fix here — the packaging decision is scripts/build_binary.sh's and has release-asset consequences (install.sh, checksums.txt, the clean host (published asset) job all assume one file per target).

Reproduce

curl -fsSL https://raw.githubusercontent.com/alplabai/tan-cli/main/install.sh | sh -s -- --version v0.5.0-rc3
/usr/bin/time -p tan --version

Reported after a full command sweep of rc3 on a real host; timing was measured because a user reported the CLI felt "extremely slow", and the number turned out to be far worse than "felt".

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingpython-portRust-to-Python port of the tan command surfacerelease-blockerBlocks the next release; must land before the tag

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions