Distill best practices from agent-runtimes M1-M3 memory files

12 additions/updates across 5 best-practice files:
- docker-uid-matching: userdel simplification, SSH agent socket UID match
- debugging: GIT_SSH_COMMAND scope limitation
- test-driven-development: subprocess mock gotcha, routing callables,
  Pydantic v2 field_validator defaults, sys.exit at module level
- spec-driven-development: multi-agent orchestration practices (commit WIP,
  self-verify, import conventions, assembly budget)
- validation: test pre-commit hooks after adding dependencies

Source: agent-runtimes/memory/ (decisions, gotchas-docker, gotchas-python,
process-lessons, m1/m2/m3 reflections)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Paul O'Reilly
2026-03-25 11:11:54 +13:00
parent e7c8214499
commit 1b5e73dc54
6 changed files with 91 additions and 16 deletions

View File

@@ -433,6 +433,24 @@ pytest tests/ -m integration
pytest tests/ -m hidden
```
## Python Testing Gotchas
### `subprocess.run(check=True)` Is Invisible to Mocks
When you mock `subprocess.run`, the mock replaces the entire function — including the `check=True` logic that raises `CalledProcessError`. A mock returning `CompletedProcess(returncode=1)` won't trigger the exception even though the real code uses `check=True`. To test failure paths, use `side_effect=CalledProcessError(...)` explicitly.
### Use Routing Callables for Multi-Call Subprocess Mocks
When a function calls `subprocess.run` multiple times (e.g., git config, add, diff, commit, push), a fixed `side_effect` list is fragile and breaks when call order changes. Instead, use a routing callable that inspects the command: `mock_run.side_effect = lambda cmd, **kw: route_by_command(cmd)`. Clearer, more maintainable, and self-documenting.
### Pydantic v2 `@field_validator` Doesn't Fire for Default Values
`@field_validator('field_name')` never runs when the field takes its default value (e.g., `None`). Cross-field validation logic (e.g., "if type is X then field Y is required") silently passes when the dependent field is omitted. Use `@model_validator(mode='after')` for any validation that depends on multiple fields or needs to fire even when fields take defaults.
### Never `sys.exit()` at Module Level
`sys.exit()` in an `except ImportError` block at module level kills pytest collection entirely — all tests fail, not just the ones for that module. Use a flag pattern instead: `_HAS_DEPENDENCY = False` in the except block, then check `if not _HAS_DEPENDENCY: return 1` inside the function. This allows the module to be imported and mocked even when the optional dependency is missing.
## Anti-Patterns
### Tests that mirror implementation