SessionStart hook crashed at module-import time on PEP 604 union annotations when the AI CLI host spawned python3 as macOS system 3.9 — even though the user’s shell python3 was 3.11. Not breaking; no --migrate required. Also relaxes the declared Python floor from 3.10 to 3.9 so the macOS system python3 is supported out of the box.
Bug Fixes
Hook PEP 604 annotation crash
packages/cli/src/templates/shared-hooks/session-start.py and inject-subagent-context.py did not declare from __future__ import annotations, so PEP 604 union annotations (str | None, dict | None) were evaluated eagerly when Python processed the def statement. On any python3 < 3.10 the module aborted with:
python3 --version reported 3.11.12 (homebrew), but the AI CLI host spawned the hook subprocess with a minimal PATH that did not include /opt/homebrew/bin. env python3 resolved to /usr/bin/python3 → macOS system 3.9, which does not implement PEP 604 at expression-eval time.
packages/cli/src/templates/shared-hooks/statusline.py plus the copilot/codex copies of session-start.py already carried the future import; the two canonical shared-hooks/*.py files were the outliers.
Fix — add one line immediately after the module docstring:
| File | Change |
|---|---|
packages/cli/src/templates/shared-hooks/session-start.py | +from __future__ import annotations |
packages/cli/src/templates/shared-hooks/inject-subagent-context.py | +from __future__ import annotations |
from __future__ import annotations (PEP 563) makes all annotations lazy strings, so PEP 604 syntax in annotations is safe on Python 3.7+. Runtime union expressions — e.g. isinstance(x, int | str) — are not rescued and still require 3.10+; neither hook uses them.
Improvements
Python floor relaxed from 3.10 to 3.9
packages/cli/src/commands/init.ts now sets MIN_MINOR = 9. Rationale: macOS Ventura / Sonoma / Sequoia all ship /usr/bin/python3 at 3.9.6, and Trellis’s distributed templates (both shared-hooks/*.py and trellis/scripts/**/*.py) were empirically verified against CPython 3.8–3.13 via a full package-import matrix — 30/30 files load cleanly on every tested version.
| Change | Location |
|---|---|
MIN_MINOR = 10 → 9 | packages/cli/src/commands/init.ts |
Warning text Python ≥ 3.10 → Python ≥ 3.9 | packages/cli/src/commands/init.ts (2 occurrences) |
Python ≥ 3.10 → Python ≥ 3.9 | README.md |
| Quickstart Prerequisites table | docs-site/quickstart.mdx + docs-site/zh/quickstart.mdx (new section) |
/tmp/trellis-py-compat/ during development (not committed). Python 3.8 is not supported — EOL 2024-10, and declaring support would incur backport obligations whenever an unmaintained-Python CVE surfaces.
Init now follows the same OS-aware Python command policy as templates
The template layer already rendered{{PYTHON_CMD}} as python on Windows and
python3 on macOS/Linux, but packages/cli/src/commands/init.ts still probed
python3 first everywhere and only fell back to python. That meant the
Windows status message, generated hook commands, and init’s own
init_developer.py bootstrap path were talking about different interpreters.
trellis init now uses the same platform rule in both places:
| Platform | Generated command | Init probe / bootstrap command |
|---|---|---|
| Windows | python | python --version, python .trellis/scripts/init_developer.py ... |
| macOS / Linux | python3 | python3 --version, python3 .trellis/scripts/init_developer.py ... |
| File | Change |
|---|---|
packages/cli/src/configurators/shared.ts | Export shared OS → Python command helper used by template rendering |
packages/cli/src/commands/init.ts | Reuse shared helper for version probe, Windows notice, and init_developer.py invocation |
packages/cli/test/commands/init.integration.test.ts | Regression coverage for init bootstrap command + soft warning path |
packages/cli/test/commands/init-internals.test.ts | Unit coverage for Python version floor warning behavior |
Upgrade
Existing projects:cross-platform-thinking-guide.md template. Pristine installs apply silently; locally-modified copies land on the standard confirm prompt.
Fresh install: