Skip to content

ci: add a ruff (pyflakes) lint gate - #450

Open
ADanMan wants to merge 1 commit into
andrewyng:mainfrom
ADanMan:ci/ruff-pyflakes-gate
Open

ci: add a ruff (pyflakes) lint gate#450
ADanMan wants to merge 1 commit into
andrewyng:mainfrom
ADanMan:ci/ruff-pyflakes-gate

Conversation

@ADanMan

@ADanMan ADanMan commented Aug 5, 2026

Copy link
Copy Markdown

Fixes #34 (the Python half — CI has no linter/type-checker at all today; the GUI's eslint/tsc half looks already covered by #419's typecheck-in-gui-unit).

What

Adds a lint job to .github/workflows/ci.yml running ruff check coworker tests, and a [tool.ruff] config in pyproject.toml.

Deliberately narrow rule selection: select = ["E9", "F"] — syntax errors + pyflakes only. No style rules, no formatter, nothing to bikeshed about. This is exactly the "cheapest way to catch a whole class of latent bugs (dead imports, undefined names)" the issue asks for, without opening a scope debate about house style. target-version = "py312" is pinned to match what CI actually runs (rather than derived from the >=3.10 requires-python floor), which otherwise makes ruff flag ExceptionGroup (stdlib-builtin only since 3.11) as an undefined name in test_mcp_connectors.py — a false positive, not a real 3.10 incompatibility, since CI only runs 3.12.

The issue suggested "start report-only, then ratchet to blocking." With this narrow a rule set the repo was already 33 violations away from clean, so I fixed those and shipped it blocking from the start rather than adding a report-only phase that would just get skipped over.

What it found and fixed (33 pre-existing violations)

  • 28 dead imports across coworker/ and tests/ — plain unused-import cleanup, ruff --fix after manual review of each hit (see below).
  • 2 redundant local re-imports in manager.py: approval_outcome() and _scheduled_approver() each had their own from ..engine import ApprovalOutcome, shadowing the name already imported at module level (line 40). That shadowing is why ruff flagged the module-level import as unused — the bug was the local duplicates, not the import itself, so I removed the two local ones and kept the module-level one.
  • 1 unused local variable (events = _collect(...) in test_plan_mode.py, result never read).

One deliberate non-removal: coworker/permissions.py re-exports SHELL_TOOL and WRITE_TOOLS from .risk with an explicit comment — # re-exported for back-compat (manager.py imports WRITE_TOOLS). Ruff can't see that manager.py depends on this re-export path, so it flagged both as unused-in-file. I left them in place and added # noqa: F401 rather than deleting what's an intentional public re-export.

Testing

  • ruff check coworker tests → clean.
  • Full suite: 1112 passed, 1 skipped (same venv/extras setup as fix(providers): mark Qwen3.8 Max Preview as vision-capable #449: uv venv --python 3.11 + pip install -e ".[dev,bedrock,messaging]").
  • Every import removal was checked individually first (grepped for re-export markers and cross-module references) before running ruff --fix — the permissions.py case above is why that mattered.

No static-analysis gate existed for the Python side — CI ran pytest
only, nothing checked for dead imports, unused variables, undefined
names, or syntax errors before review.

Add ruff, scoped deliberately narrow to start (owner call, per issue
andrewyng#34): select = ["E9", "F"] — syntax errors + pyflakes. No style rules,
no formatter, nothing to bikeshed; just the checks with essentially
zero false positives that catch real latent bugs. target-version is
pinned to py312 (matching what CI actually runs) rather than derived
from the 3.10 requires-python floor, which would otherwise flag
`ExceptionGroup` (3.11+) as an undefined name in test_mcp_connectors.py.

Fixed what it found (33 violations, all pre-existing):
- 28 dead imports across coworker/ and tests/. Two (SHELL_TOOL,
  WRITE_TOOLS in permissions.py) are an intentional back-compat
  re-export per the existing comment on that import block — left in
  place with `# noqa: F401` rather than removed.
- 2 redundant local `from ..engine import ApprovalOutcome` in
  manager.py that shadowed the already-imported module-level name,
  which is what made that module-level import itself look unused.
- 1 unused local variable in a test.

This is the Python half of andrewyng#34; the GUI's eslint/tsc half looks
already covered by andrewyng#419 (typecheck in gui-unit).

Full suite still green: 1112 passed, 1 skipped.
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.

[CI] No lint / format / type-check gate (ruff / mypy / eslint / tsc)

1 participant