fix: bound ruff to >=0.16,<0.17, make the lint gate real, fix the defects it found - #9
Merged
Merged
Conversation
`ruff>=0.1.0` was unbounded and `[tool.ruff]` set only `line-length`, so this
repo inherited whatever default rule set the resolved ruff happened to ship.
ruff 0.16.0 grew that set from 59 rules to 413. This bounds the dependency to
`>=0.16,<0.17`, pins `target-version = "py310"`, keeps the FULL default set
deliberately, and clears all 261 findings.
The CI lint step could not fail -- it carried `|| true` AND
`continue-on-error: true` -- so it had never enforced anything. It is now a
blocking `ruff check .` over the whole repository. The companion
`ruff format --check` step is deleted rather than made real: 90 of 143 files
do not match `ruff format`, and reformatting belongs in its own commit.
Real defects the sweep exposed, all fixed with regression tests:
* scanner._extract_segmentation_info swallowed an unparseable
`processing_timestamp` with a bare `except: pass`, silently substituting the
file's mtime as the segmentation date -- and swallowing KeyboardInterrupt.
* Both `_get_core_css` implementations fell back to a truncated inline
stylesheet in total silence when the packaged core.css could not be read, so
a page rendered with visibly different styling and nothing said why.
* Both screenshot scripts returned 0 unconditionally. A run in which every
scenario raised still printed "completed successfully" and exited 0, and
`openadapt-viewer screenshots` propagates that status verbatim.
* Two playwright interactions used `except Exception: pass`, so a screenshot
named "search focused" / "recording filter open" could show an unfocused box
or a closed dropdown and be reported as generated.
* real_data_loader used `.lstrip("../")`, a character-set strip, where a prefix
strip was meant.
* test_minimal_viewer.py called the `site`-injected builtin `exit`, absent
under `python -S` and in frozen builds.
No timestamp a user reads was moved: every naive datetime was fixed with a
trailing `.astimezone()`, which anchors the local zone without changing the
wall clock. The two rendered timestamps (`created_at_formatted` and the
`Created:` column of `catalog list`) go through `strftime` with no `%z`, and a
test pins their output byte-for-byte against the pre-change expression.
`uv lock --upgrade-package ruff` also refreshed a lockfile that was stale
against pyproject.toml: `imageio` (a declared runtime dependency) and its
numpy/pillow transitives were absent, and the project was recorded at 0.1.0.
No existing dependency version changed.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NyCHrzA1psrKMFfroYbzaM
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.
What this fixes
pyproject.tomldeclaredruff>=0.1.0(unbounded) and[tool.ruff]set onlyline-length = 100— notarget-version, noselect. That means the rule set this repo is linted against was whatever the resolved ruff happened to default to. ruff 0.16.0 grew its default set from 59 rules to 413.uv.lockpinned 0.14.13, so nothing had noticed yet.The CI lint step could not have noticed either. Verbatim, before:
|| trueandcontinue-on-error: true— belt and braces. This gate has never enforced anything. At the moment it was made real the tree had 261 findings.The rule-set decision: keep the full default set
Not narrowed. The residual after safe autofix was 102 findings across a 13.8k-line tree — small enough to read one by one, and reading them is what surfaced the defects below. A
select = ["E","F","I","W"]fallback would have reported 57 findings and found none of them. Predictability comes from the bound instead:ruff>=0.16,<0.17, with the reasoning inline inpyproject.toml. ruff only grows its default set in a minor release, so the rule set cannot move until a person raises that ceiling and owns the new findings in the same change.target-version = "py310"is now explicit rather than inferred fromrequires-python.The fake gate
ruff checkis now a blockingruff check .over the whole repository (src, tests, scripts, root-level scripts) — that is the scope the residual was cleared against, and it costs no extra CI time.ruff format --checkis deleted, not made real. 90 of 143 files (31 of 41 undersrc/) do not matchruff formattoday. Making it blocking here would mean reformatting the tree inside a lint-configuration change and burying every real fix in whitespace. A check that cannot fail is worse than no check — it reads as coverage that does not exist — so it is gone until someone does the reformat as its own reviewable commit. The reasoning is in the workflow file.Real defects (not cosmetic)
src/openadapt_viewer/scanner.py:229except: pass(E722+S110)processing_timestampsilently substituted the file's mtime as the segmentation date. Also swallowedKeyboardInterrupt.src/openadapt_viewer/builders/page_builder.py:314src/openadapt_viewer/viewers/capture/generator.py:411except Exception: pass(S110+BLE001)core.cssfell back to a truncated inline stylesheet in total silence — the page rendered with visibly different styling and nothing said why.scripts/generate_segmentation_screenshots.py:499scripts/generate_comprehensive_screenshots.py:420return 0unconditionally (found via BLE001)✓ Screenshot generation completed successfully!and exited 0.cli.run_screenshots_commandpropagates that status verbatim, soopenadapt-viewer screenshotsreported success having produced nothing.scripts/generate_segmentation_screenshots.py:345,368except Exception: pass(S110)page.clickstill produced a screenshot named "search focused" showing an unfocused box, or "recording filter open" showing a closed dropdown, and reported it as generated. Sibling handlers in the same file already warned.src/openadapt_viewer/viewers/benchmark/real_data_loader.py:127.lstrip("../")(B005)startswith, but wrong for any other input. Nowremoveprefix("../").test_minimal_viewer.py:123,128exit(1)(PLR1722)site-injected builtin, absent underpython -Sand in frozen builds, where this would have died withNameErrorinstead of exiting 1.Straight answers on the categories you asked about
cli.py:422; every one of the 21 inspectsresult.returncodeexplicitly, socheck=Falseis what the surrounding code wants and raising would break them. Added explicitly with a one-line note per file.except Exception as eon the scanner's own S110 pair. All narrowed to concrete exception tuples plus a warning; none silenced.stats_parser/clean_parserare argparse subparsers that take no arguments of their own;nameinrun_catalog_commandrecomputed exactly whatscanner._extract_recording_infoalready derives, andargs.nameis applied torecording.nametwo lines later. Assignments dropped.sqlite3.Error,OSError,ValueError,playwright.sync_api.Error, …), each with an inline note on what it covers and why. 6 kept as deliberate# noqa: BLE001: three top-of-process handlers and three per-item loop handlers that must not abandon a batch — those now record the failure so the exit status is honest. No repo-wideignore = ["BLE001"].Did any DTZ fix move a timestamp a user sees? No.
Every one of the 40 naive-datetime sites was fixed with a trailing
.astimezone(), which attaches the machine's local zone without moving the wall clock. This was the point: a viewer shows the time a recording happened on this machine, and converting to UTC would have silently shifted what the user reads.Two timestamps are actually rendered to users —
created_at_formattedin the segmentation catalog, and theCreated:column ofopenadapt-viewer catalog list. Both go throughstrftimewith no%z, so their output is byte-identical before and after.tests/test_scanner_and_catalog.py::TestTimestampsRenderUnchangedpins that against the literal pre-change expression.Only
.isoformat()output changes, gaining a+HH:MMsuffix — thegenerated_atmetadata keys. That makes an ambiguous emitted value unambiguous, not different.DataLoader.parse_datetimenow returns consistently aware values for parsed input (offset-bearing ISO strings are left alone; naive ones are anchored to local without moving). Theisinstance(datetime)passthrough is untouched, since its docstring andtest_parse_datetime_objectdefine it as identity.Rules disabled
One entry, narrowly scoped:
Both
__all__lists are grouped by subsystem with# Core/# Components/# Builders/# Viewers/# Catalogheadings; an alphabetical sort strands every heading against unrelated names. Justification is inline.Plus 6 inline
# noqa: BLE001and 2 inline# noqa: DTZ006, each with a reason on the adjacent line. The DTZ ones are in the test that deliberately uses the pre-change naive expression as its expected value.Tests
tests/test_scanner_and_catalog.py(new, 11 tests) and 2 tests appended totests/test_segmentation_screenshots.py. Verified againstorigin/mainin a throwaway worktree: 5 of them fail on the pre-change code (the scanner warning, the KeyboardInterrupt passthrough, both CSS-fallback warnings, and the screenshot exit status), and all pass here.Lockfile
uv lock --upgrade-package ruffmovedruff 0.14.13 -> 0.16.0.revisionwas already 3, so there is no format churn from this change.There is other lockfile movement, and it is not from the ruff bump: the committed
uv.lockwas already stale againstpyproject.toml.imageio— a declared runtime dependency — and itsnumpytransitives were absent from the lock entirely, and the project was recorded at0.1.0whilepyproject.tomlsays0.2.0. Anyuv syncregenerates this, including the one CI runs on every job, so CI has been silently relocking. No existing dependency version changed — the only diffs areruff 0.14.13 -> 0.16.0,openadapt-viewer 0.1.0 -> 0.2.0, and the addition ofimageio 2.37.4+numpy.Verification (local, ruff 0.16.0)
ruff format --check src/openadapt_viewer/still reports 31 files — pre-existing, untouched, and the reason that step is deleted rather than made blocking.Release
No release fires from this.
.github/workflows/release.ymlison: workflow_dispatchonly as of #8, so thefix:prefix has no effect here; a release only happens when someone runs the workflow by hand.fix:is used because this PR fixes real defects, not just configuration.Out of scope, but worth someone's attention
src/openadapt_viewer/viewers/benchmark/real_data_loader.py:126hardcodes an absolute developer home path (/Users/abrichr/oa/src) in shipped library code, so those screenshot paths resolve on exactly one machine. Not a lint finding and not touched here.🤖 Generated with Claude Code
https://claude.ai/code/session_01NyCHrzA1psrKMFfroYbzaM