Skip to content

ENG-1646: self-heal scratchpad venv creation instead of retrying an identical failure - #359

Open
torrmal wants to merge 1 commit into
stagingfrom
jorge/eng-1646-scratchpad-venv-self-heal
Open

ENG-1646: self-heal scratchpad venv creation instead of retrying an identical failure#359
torrmal wants to merge 1 commit into
stagingfrom
jorge/eng-1646-scratchpad-venv-self-heal

Conversation

@torrmal

@torrmal torrmal commented Aug 15, 2026

Copy link
Copy Markdown
Contributor

Summary

Fixes ENG-1646.

uv venv normally symlinks a new venv's bin/python straight to the base interpreter. On at least one real machine, when invoked from inside the packaged Cowork desktop app's process tree, uv instead wrote bin/python as a freshly-copied, merely ad-hoc-signed launcher — missing its own libpythonX.Y.dylib — and macOS's AMFI killed it outright the moment anything tried to run it:

kernel: (AppleMobileFileIntegrity) AMFI: '.../bin/python' has no CMS blob?
kernel: (AppleMobileFileIntegrity) AMFI: '.../bin/python': Unrecoverable CT signature issue, bailing out.

Because the failure is deterministic per environment (not transient), all 3 of _ensure_venv()'s retries repeated the identical uv venv call and failed identically — so restarting the app, renaming the scratchpad, or waiting and retrying never helped. The full repro/diagnosis is in the Linear issue.

Changes

  • _create_venv() now checks whether uv actually produced a symlink for bin/python; if it instead wrote a copy, _repair_copied_launcher() replaces it with a direct symlink to the resolved base interpreter. Every symlinked interpreter observed in testing worked; every copied one was killed by AMFI.
  • _ensure_venv()'s retry loop now escalates strategy instead of repeating the same doomed call: the final attempt bypasses uv entirely and falls back to stdlib venv.create(symlinks=True), which has been reliable in every environment this was tested against.
  • The venv directory is no longer nuked after the final failed attempt — it's left in place for inspection instead of erasing the only evidence of what went wrong.
  • Failures now carry real diagnostic detail (_diagnose_broken_interpreter(): symlink vs. copy, file size, and codesign status on macOS) instead of going silent whenever _last_verify_error was never set (e.g. the interpreter file simply doesn't exist).

Test plan

  • uv run pytest tests/test_local_venv_provisioning.py -v — 23 passed (6 new, covering the repair, the escalation order, directory preservation, and the new diagnostic detail)
  • uv run pytest tests/test_scratchpad.py tests/test_chat_scratchpad.py tests/test_scratchpad_flavor.py tests/test_scratchpad_utf8.py tests/test_scratchpad_watchdog_contracts.py tests/test_scratchpad_observer_dispatch.py -q — 211 passed, no regressions
  • Manual verification on the machine where this was originally observed failing in production

🤖 Generated with Claude Code

…dentical failure

uv venv occasionally writes bin/python as a copied, merely ad-hoc-signed
launcher instead of a symlink to the base interpreter, and the copy is
missing its own libpythonX.Y.dylib. macOS's AMFI kills it outright
("Unrecoverable CT signature issue"), and since the failure is deterministic
per environment, all 3 retries repeated the identical uv call and failed
identically.

- _create_venv() now repairs a copied launcher into a direct symlink after
  uv reports success, since every symlinked interpreter we observed worked.
- _ensure_venv()'s final retry escalates to the stdlib venv.create fallback
  instead of repeating the same uv invocation a third time.
- The venv directory is preserved after the final failed attempt (previously
  nuked unconditionally), and the raised error now includes symlink/size/
  codesign detail via _diagnose_broken_interpreter() instead of going silent
  once _last_verify_error is unset.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@torrmal
torrmal requested a review from sejubar August 15, 2026 00:31
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.

1 participant