fix(#856): gate scripts/ and contrib/ with ruff, scripts/ with pyright - #858
Merged
Merged
Conversation
scripts/ (8 files) was checked by nothing: 'make lint'/'fmt' covered src/ and
tests/, and pyrightconfig.json included only src/decafclaw. contrib/ (25
files) had the same ruff gap and was reached only by pytest via testpaths.
Running the missing gate by hand surfaced a live bug in build-eval-fixtures.py:
config.data_home = tmp
config.agent_id = 'fixture'
Neither is a field on Config - they live on config.agent - and Config is not
frozen, so these silently created undeclared attributes that nothing read. The
redirect no-opped, so index_entry wrote to the real
data/{agent_id}/workspace/embeddings.db, polluting the live embedding index.
The hand-assembled pickup path was independently wrong too:
Path(tmp)/'workspace'/'fixture' vs workspace_path's data_home/agent_id/
workspace, so the shutil.move would have failed even once the redirect worked.
'make build-eval-fixtures' therefore clobbered real data and then crashed.
Fixed with a fixture_config() helper using nested dataclasses.replace per the
CLAUDE.md dataclass-copy convention, and the pickup path is now derived from
build_config.workspace_path rather than hand-assembled.
'check' is now composed from 'lint typecheck check-js' instead of repeating
their commands. It carried its own 'ruff check src/ tests/' line, so widening
'lint' alone would not have widened what CI gates - the same duplication that
let check-message-types and check-js run nowhere before #854.
contrib/ joins ruff (clean today) but NOT pyright: 'pyright contrib/' reports
21 errors, which is its own cleanup and should not ride along here.
Tests written first and observed failing on the missing helper. The third test
pins the root cause - that data_home/agent_id are not Config fields, so
attribute assignment is a silent no-op - so a future 'simplification' cannot
reintroduce it.
Teeth-checked: an F821 under scripts/ and under contrib/ each make 'make lint'
exit 2, and a type error under scripts/ makes pyright exit 1.
Contributor
There was a problem hiding this comment.
🟡 Changes recommended
Resolve the Makefile ordering hazard and make the Pyright diagnostic enforceable.
Get a fresh assessment by requesting another Copilot review.
Pull request overview
Expands Ruff/Pyright coverage and fixes eval-fixture configuration and database path handling.
Changes:
- Adds
scripts/andcontrib/to Ruff coverage. - Adds
scripts/to Pyright and composesmake check. - Uses nested configuration replacement with regression tests.
File summaries
| File | Summary | Final review comment |
|---|---|---|
tests/test_build_eval_fixtures.py |
Tests fixture configuration isolation. | None. |
scripts/build-eval-fixtures.py |
Fixes temporary configuration and database path handling. | Nit (1 vote): Add coverage asserting main passes build_config.workspace_path / "embeddings.db" to shutil.move. |
pyrightconfig.json |
Includes scripts/ in Pyright. |
Moderate (3 votes): The warning-only diagnostic does not fail Pyright; promote the rule or use --warnings if enforcement is intended. |
Makefile |
Expands Ruff coverage and composes check. |
Moderate (1 vote): Preserve an ordering barrier so parallel checks cannot read files while message types are being generated. |
Review details
Suppressed comments (2)
Makefile:105
- This refactor changes
check-message-typesfrom a prerequisite phase before the checks into a sibling prerequisite. Withmake -j check, the generator rewritessrc/decafclaw/web/message_types.pyand the JS/TS outputs whilelint, Pyright, orcheck-jscan read them, so a check can fail nondeterministically on a partially written generated file (the generator useswrite_text). Preserve an ordering barrier from the generator to the other gates when composing this target.
check: install-js check-message-types lint typecheck check-js
scripts/build-eval-fixtures.py:69
- The new tests exercise
fixture_config, but none exercisesmain's pickup path. If the oldPath(tmp) / "workspace" / "fixture"is reintroduced here, all three tests still pass because they only assert the helper'sworkspace_path, not the source passed toshutil.move. Add a focused seam/test (or mockmain's dependencies) that asserts the move source isbuild_config.workspace_path / "embeddings.db".
built_db = build_config.workspace_path / "embeddings.db"
- Files reviewed: 4/4 changed files
- Comments generated: 1
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| @@ -1,5 +1,5 @@ | |||
| { | |||
| "include": ["src/decafclaw"], | |||
| "include": ["src/decafclaw", "scripts"], | |||
This was referenced Sep 16, 2026
lmorchard
added a commit
that referenced
this pull request
Sep 16, 2026
Doc currency for #852-#858. AGENTS.md is a symlink to this file, so Codex picks these up automatically. - Running: adds check-tui, test-tui, prune-worktrees{,-dry}; corrects 'make check' (composed from install-js, check-message-types, lint, typecheck and check-js) and 'make lint' (described as 'Compile-check'; it is ruff over four directories, and was inaccurate before this session too — fixed because it sits in the same list being corrected for accuracy). - Workflow: a new convention for the defect that recurred four times in one session — a gate that exists in the Makefile and still runs nowhere. Compose targets rather than duplicating their commands, confirm CI invokes the target and read the job log, and probe every gate for teeth. Notes that pyright exits 0 when it reports only warnings, which makes a pyrightconfig.json addition report without failing. Both composition lists are checked against Makefile:105 programmatically, not by eye — an earlier draft listed only three of the five prerequisites.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #856.
scripts/(8 files) was checked by nothing:make lint/fmtcoveredsrc/and
tests/, andpyrightconfig.jsonincluded onlysrc/decafclaw.contrib/(25 files) had the same ruff gap and was reached only by pytest viatestpaths.The live bug the missing gate was hiding
scripts/build-eval-fixtures.py:Neither is a field on
Config— they live onconfig.agent.Configisn'tfrozen, so these silently created undeclared attributes that nothing read.
Two independent failures followed:
index_entrywrote to the realdata/{agent_id}/workspace/embeddings.db— polluting the live embeddingindex.
Path(tmp)/"workspace"/"fixture"vsworkspace_path'sdata_home/agent_id/workspace. Wrong component order, so theshutil.movewould have failed even once the redirect worked.
So
make build-eval-fixturesclobbered real agent data and then crashed.This is exactly the anti-pattern CLAUDE.md names — "Don't
setattrundeclaredattributes on Config/Context" — and pyright reports it for free.
Fixed with a
fixture_config()helper using nesteddataclasses.replaceperthe dataclass-copy convention, and the pickup path is now derived from
build_config.workspace_pathinstead of hand-assembled.checkis now composed, not duplicatedcheckcarried its ownruff check src/ tests/line, so wideninglintalonewould not have widened what CI gates. It is now:
check: install-js check-message-types lint typecheck check-jsSame duplication that let
check-message-typesandcheck-jsrun nowherebefore #854 — worth removing rather than adding a third copy.
Scope calls, both measured
contrib/joins ruff but not pyright.ruff check contrib/is cleantoday, so it's free.
pyright contrib/reports 21 errors — its owncleanup, which shouldn't ride along here.
The
ruff formatsweep is dropped, and #856's checklist item for it issuperseded by #857. The premise was that
scripts/was a formattingoutlier; it isn't.
ruff format --checksays 312 files acrosssrc/andtests/would also be reformatted, so the repo has never been formatted atall. I ran the 24-file sweep, verified it semantically inert, then reverted it:
formatting 2 of 4 directories is less consistent than formatting none, and
make checkdoesn't check formatting anyway, so it buys the gate nothing.#857 covers the real problem, which is that
make fmtproduces a 312-file difffor anyone who runs it.
Verification
fixture_config.The third test pins the root cause — that
data_home/agent_idare notConfigfields, so attribute assignment is a silent no-op — so a future"simplification" can't reintroduce it. The module is loaded by path because
its filename is hyphenated.
F821underscripts/and undercontrib/each makemake lintexit 2; a type error underscripts/makes pyright exit 1.
make checkexit 0 (ruff check src/ tests/ scripts/ contrib/→ all passed;pyright → 0 errors, 0 warnings;
tsc --noEmit).make test→ 3897 passed,2 skipped.
24 files (0 genuine semantic differences) by comparing
ast.dumpwithadjacent string
Constants insideJoinedStrmerged — ruff joins implicitf-string concatenation, which changes the raw token stream and AST without
changing the runtime value. Recorded in make fmt is a 312-file landmine: the tree has never been ruff-formatted, and formatting is ungated #857 for whoever does the real sweep.
One thing worth a decision
pyrightexits 0 when it reports only warnings, andpyrightconfig.jsonsetsreportAttributeAccessIssue: "warning"— which isthe rule that flagged this very bug. So including
scripts/makes pyrightreport this bug class without failing on it.
uv run pyright --warningswould make warnings fatal, and it's free today (the repo is at 0 warnings) —
but it's a strictness change to the whole gate and re-escalates a rule that was
deliberately downgraded, so it isn't bundled here.