Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions .githooks/pre-push
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,26 @@ def _git(*args: str) -> subprocess.CompletedProcess:
)


def _drop_repo_env() -> None:
"""Remove the variables that bind a git process to THIS repository.

git exports GIT_DIR to hooks (in a linked worktree it is always set), and
every layer below inherits os.environ. The review lenses are agents that
may run the test suite; a test that does `git init` / `add -A` / `commit`
in a temp dir then writes to the pushing repo instead. On 2026-10-29 one
push left 5 fixture commits on the branch (the tree reduced to 8 files) and
`[user] name = t` in the shared .git/config. The hook has already chdir'd
to the worktree root, so git rediscovers the same repo without them.
`--local-env-vars` is git's own list: what it clears for a submodule.
"""
names = _git("rev-parse", "--local-env-vars").stdout.split() or [
"GIT_DIR", "GIT_WORK_TREE", "GIT_INDEX_FILE", "GIT_COMMON_DIR",
"GIT_OBJECT_DIRECTORY", "GIT_PREFIX",
]
for name in names:
os.environ.pop(name, None)


def _source_local_env(repo_root: Path) -> None:
"""Parse simple KEY=VALUE lines from $MINI_ORK_HOME/config/local.sh.

Expand Down Expand Up @@ -176,6 +196,8 @@ def main() -> int:
except OSError:
return 0

_drop_repo_env()

# Per-developer local env preferences (non-secret).
_source_local_env(REPO_ROOT)

Expand Down
30 changes: 18 additions & 12 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -58,18 +58,24 @@ jobs:
python-tests:
name: python tests (${{ matrix.python-version }} / ${{ matrix.shard }})
runs-on: ubuntu-latest
# The unit shards run 22-33 min and drift with every added test file (the
# odd/even split reshuffles); 35 cancelled a passing 3.12/unit-a at 35.4
# min on 2026-10-28 (a re-run took 28.6). Headroom, not a ceiling to aim at.
timeout-minutes: 50
# Each shard runs its tests on every runner core (pytest-xdist, below).
# Serially the unit shards took 23-39 min and 50 was the timeout; a shard
# that now needs more than 30 is a hang or a regression worth seeing.
timeout-minutes: 30
strategy:
fail-fast: false
matrix:
python-version: ["3.11", "3.12"]
# Sharding: the suite is ~2,090 tests dominated by bash<->python
# parity subprocess costs (~14-17 min as one job). Split the big
# tests/unit tier odd/even by file (interleave balances the slow
# clusters) and run everything else as a third shard.
# Sharding: tests/unit is ~14,000 tests whose time is spread thin —
# subprocess and git work in hundreds of files, the slowest file ~4%
# of the total — so parallelism, not trimming single tests, is what
# moves it. Split it odd/even by file and run everything else as a
# third shard; within a shard, xdist uses all cores (`-n auto`). Both
# tiers pass on 8 workers (unit: 14,000 passed, 0 failed; rest: 734
# passed, its one local failure reproduces serially; 2026-10-29), so
# tests do not share state across processes.
# `find`, not `ls tests/unit/*.py`: the glob never reached
# tests/unit/acp/, so its 28 tests ran in no shard at all.
shard: [unit-a, unit-b, rest]
steps:
- uses: actions/checkout@v7
Expand All @@ -90,7 +96,7 @@ jobs:
# tests/unit/test_acp_agent_py.py imports at module scope; without
# the extra the whole shard dies at collection, not on an assertion.
pip install -e ".[yaml,acp]"
pip install pytest fastapi uvicorn httpx2
pip install pytest "pytest-xdist>=3.5" fastapi uvicorn httpx2

# The web smoke suite points the FastAPI route handlers at a real
# state.db; init bootstraps the full schema so those tests run
Expand All @@ -100,15 +106,15 @@ jobs:

- name: Run pytest (unit shard A)
if: matrix.shard == 'unit-a'
run: ls tests/unit/*.py | awk 'NR % 2 == 1' | xargs python -m pytest -q -p no:cacheprovider
run: find tests/unit -name 'test_*.py' | sort | awk 'NR % 2 == 1' | xargs python -m pytest -q -p no:cacheprovider -n auto

- name: Run pytest (unit shard B)
if: matrix.shard == 'unit-b'
run: ls tests/unit/*.py | awk 'NR % 2 == 0' | xargs python -m pytest -q -p no:cacheprovider
run: find tests/unit -name 'test_*.py' | sort | awk 'NR % 2 == 0' | xargs python -m pytest -q -p no:cacheprovider -n auto

- name: Run pytest (root + non-unit layers)
if: matrix.shard == 'rest'
run: python -m pytest tests/ -q -p no:cacheprovider --ignore=tests/unit
run: python -m pytest tests/ -q -p no:cacheprovider -n auto --ignore=tests/unit

# The optional `runtime` extra pins `verifiers>=0.2.0,<0.5`, and that ceiling moves
# by dependabot PR (it went <0.3 -> <0.5 on 2026-10-27 with no check on the API).
Expand Down
Loading