Follow-up to the original strict-mode real-bug audit attached to PR #2291. Strict mode is not enabled in the PR — this is a one-off audit run by temporarily flipping typeCheckingMode = "strict" and reverting before commit.
The original audit listed 187 primary error sites for the two remaining real-bug rules after reportIndexIssue was eliminated in commit 4f0e2237b:
- 161 ×
reportUnnecessaryComparison - 26 ×
reportConstantRedefinition
This report classifies each site into one of three buckets:
- (A) Real bug — pyright found genuine dead code, an always-true/always-false branch, or a constant redefined with a different value. Fix is code-level: delete the dead branch, fix the wrong-direction comparison, correct the redefinition.
- (B) Type annotation too narrow — pyright thinks the value is narrower than runtime allows. The branch IS reachable at runtime, but the static types don't capture that. Fix is annotation-level: widen a parameter, mark a "constant" as a regular module-level variable, use
castat a boundary. - (C) Defensive belt-and-suspenders — pyright proves the check is statically redundant, but it guards against runtime values pyright doesn't model: data from JSON / SQL / network,
Anyfrom external libs, mocks in tests, dynamic config. Keep the check; suppress with# pyright: ignore[<rule>]and a one-line comment explaining what runtime path could violate the static assumption.
| Bucket | Count | Share |
|---|---|---|
| A real bug | 53 | 28% |
| B annotation too narrow | 111 | 59% |
| C defensive | 23 | 12% |
| Total | 187 |
The dominant pattern is (B): too-narrow annotations — fields/params declared non-Optional that are runtime-Optional, single-assign init flags written ALL_CAPS that pyright sees as Final, and external library stubs that don't model their real Optional-or-raise contract (cv2, paramiko, GitPython, google-auth, pyads, OPCUA wire types). Fixing these is annotation-level and shouldn't need any runtime behaviour change.
The (A) cohort is concentrated in:
src/pap_rob(15 — mostly redundantis not Noneafter asserts/early returns, plus dead checks against@propertyreturns and module-level constants),src/camera(12 — frameset-returning helpers typed non-Optional that genuinely never return None, plusaiohttp.read()/bytesconfusion),src/hyperstar(12 — threeip_scaleternaries, fourget_robot_nameternaries, three already-narrowed-by-assert sites).
The (C) cohort is small and concentrated in two places: pap_rob's cast(dict, request.json) Flask handler pattern (8 sites — really one bug-class with a typed-helper fix) and the bq_client_query wrapper used across infra (3 sites).
| Module | Total | A (bug) | B (annotation) | C (defensive) |
|---|---|---|---|---|
src/hyperstar |
46 | 12 | 29 | 5 |
src/pap_rob |
39 | 15 | 14 | 10 |
src/camera |
23 | 12 | 11 | 0 |
src/robots |
13 | 0 | 11 | 2 |
tests/integration |
13 | 0 | 11 | 2 |
src/data_engine |
11 | 4 | 7 | 0 |
src/image |
9 | 0 | 9 | 0 |
src/infra |
8 | 4 | 1 | 3 |
src/synq_api |
5 | 0 | 5 | 0 |
tests/unit |
5 | 0 | 4 | 1 |
src/utils |
4 | 2 | 2 | 0 |
src/simulation |
3 | 2 | 1 | 0 |
src/camera_tools |
2 | 0 | 2 | 0 |
src/automation_tasks |
1 | 0 | 1 | 0 |
src/barcodescanner |
1 | 1 | 0 | 0 |
src/deploy |
1 | 0 | 1 | 0 |
src/devops |
1 | 0 | 1 | 0 |
src/frameset_tools |
1 | 1 | 0 | 0 |
src/viz_server |
1 | 0 | 1 | 0 |
| Total | 187 | 53 | 111 | 23 |
A handful of single-fix changes resolve a large fraction of the report:
JointTimeline.qpos/JointTimeline.action→| None— resolves 6 sites (src/hyperstar/intent_editor/auto_intent.py×4,auto_trim_service.py×2). Same dataclass widening.- Single-assign init flags re-annotated without
Final— resolves 8 sites across hyperstar (_PATCHED,_DISCOVERED,GFX_BACKEND_INITIALIZED,_HYPERSTAR_TYPES_IMPORTED,HAS_DATEUTIL,_REQUEST_TOPIC,_RESULT_TOPIC,_X). Same naming-rule pattern. pap_rob_api.pyJSON-body cast helper — resolves 8 sites inwms_interaction_api.py+pap_rob_api.pyifcast(dict, request.json)is replaced with a typed helper that returnsdict | None.world.robot_controller: RobotController | None— resolves 3 sites inpap_rob/lessons/*(lesson teardown can run before init completes).- DETR-style
C, H, W = ...axis-name unpacks renamed lowercase — resolves 8 sites acrosssrc/data_engine/models/matcher.py,src/camera/parameters.py,src/camera/femtomega/*,src/camera_tools/*,tests/unit/image/transformers/hue_transformer_test.py. Same naming-convention violation. pyads_bugfixes.pyPLC datatype dispatch annotation widened — resolves 6 sites insrc/robots/impl/kuka/ads/network/pyads_bugfixes.py(type(plc_datatype) is tuplepattern).Frame.data/Frame.depth_data/Frame.color_dataraise-vs-Optional duality — resolves ~5 sites insrc/camera/calibration.pyandsrc/camera/frameset.pyif the public properties are clarified (or callers move to the.depth/.colorOptional accessors).
These 7 fix-clusters cover 44 of the 187 sites (24%) with maybe a dozen actual code changes between them.
- B
src/automation_tasks/envelope_pick_and_place/main.py:60—SicsformerRvModel.request_resultis annotated-> PickAndPlaceModelResultbut the caller defensively guards against null results alongsidenot res.actions. Widen the base method return toPickAndPlaceModelResult | None.
- A
src/barcodescanner/barcodescanner.py:126—dev_file.read(struct_len)on a binary file always returnsbytes, possibly emptyb''at EOF. Theif data is not Noneguard is dead; if the intent is to skip short reads, change toif len(data) == struct_len:(orif data:).
- B
src/camera/calibration.py:178—math_calibrate_extrinsicsis annotated-> CameraExtrinsicsbut actually returnsFalseon failure; widen its return toCameraExtrinsics | Literal[False](or| None). - A
src/camera/calibration.py:350—filter_valid_arucoreturnstuple[NDArray, NDArray](never None); drop thearuco_pixel_d_coordinates is Nonecheck. - A
src/camera/calibration.py:491—CameraExtrinsics.framesets: list[FrameSet]with default[], never None; drop theframesets is Nonehalf of the guard. - B
src/camera/calibration.py:495—frameset.depth_datais the raise-on-missing property typedNDArray; either use.depth(Optional) or treat the comparison as a defensive widening. - B
src/camera/calibration.py:527— same as :495 but forframeset.color_data; should access.color(Optional) or widen. - A
src/camera/camera.py:54—camera_id = camera_config.nameandname: stris never None on the dataclass; drop the dead guard. - A
src/camera/cameras_list.py:222—config.calibrated: bool = Truecan never be None; logic was probably meant to be a different attribute oris False. - B
src/camera/femtomega/femtotest.py:93(WIDTH) — script-local pseudo-constant; rename to lowercase or annotate non-Final. - B
src/camera/femtomega/femtotest.py:93(HEIGHT) — same pattern. - A
src/camera/femtomega/femtotest.py:105—imgsrc.next_frameset()is typed-> FrameSet; implementations raise rather than return None. Drop the dead None check. - A
src/camera/femtomega/imagesource.py:98—self._femtomegawas just verified non-None at line 92 with no reassignment; the second guard is dead. - A
src/camera/femtomega/imagesource.py:170—FemtoMega.intrinsics()returnsdict[str, Any]({}on failure, never None); replace withif not device_intrinsics:. - A
src/camera/femtomega/imagesource_example.py:36—next_frameset()typed-> FrameSetand never returns None at runtime; drop dead branch. - B
src/camera/femtomega/imagesource_example.py:42(HEIGHT) — script-local pseudo-constant. - B
src/camera/femtomega/imagesource_example.py:42(WIDTH) — same. - B
src/camera/femtomega/imagesource_example.py:43(HEIGHT) — same. - B
src/camera/femtomega/imagesource_example.py:43(WIDTH) — same. - A
src/camera/fetch_frameset.py:29—Camera.get_latest_frameset()is typed-> FrameSet(never None); the None guard is dead. (Widen if you actually want this defense.) - B
src/camera/frameset.py:419—_single_frame_only(...).datais the raise-on-missing property typedNDArray; widen or use_datadirectly to keep the defensive None check. - A
src/camera/imagefile/filebased_imagesource.py:128—self.loaded_files = loaded_files or []always yields a list, sois not Noneis always True; check should beif self.loaded_files:(truthiness). - B
src/camera/parameters.py:474— fieldrvec: NDArraybut__init__doesself.rvec = kwargs.get('rvec')which can be None; widen field toNDArray | None. - B
src/camera/parameters.py:477—M: NDArray | None = Noneflagged as constant due to upper-case name; rename or# pyright: ignore[reportConstantRedefinition]. - A
src/camera/rest_imagesource.py:22—aiohttp.ClientResponse.read()returnsbytes(never None); drop the deaddata is Nonecheck.
- B
src/camera_tools/generate_video_from_imagesource_snapshots.py:533(H) — script-local pseudo-constant fromcolor_frame.shapeunpack; rename to lowercase or# pyright: ignore[reportConstantRedefinition](the existing# noqa: N806already concedes this is not a real constant). - B
src/camera_tools/generate_video_from_imagesource_snapshots.py:533(W) — same.
- B
src/data_engine/dashboard/image_api.py:61—frameset.get_data('color', ...)is annotatedNDArraybut at runtime returnsNonewhen cv2 decode fails orcamera_idmissing. WidenFrameSet.get_datareturn toNDArray | None. - B
src/data_engine/dataset/resolve_rois.py:126—pre_color_frame.dataisndarrayper annotation, but cv2 decode can leavedataas None on decode failure. WidenFrame.datatoNDArray | None. - B
src/data_engine/dataset/resolve_rois.py:126(col 38) — same site, second clause (post_color_data is None); same fix. - B
src/data_engine/health_db.py:161—_known_faulty_frameset_idsis module-levelset[UUID]but used as a "lazy-init" sentinel viais None. The annotation needsset[UUID] | None(or refactor to a separate_loadedflag). - B
src/data_engine/models/matcher.py:93—Cis the classic DETR cost-matrix math name, not a constant. Rename to lowercasec(orcost) to silence the rule. - B
src/data_engine/models/matcher.py:158— same case in second matcher class; same fix. - A
src/data_engine/processing/batch_processor.py:265—frameset_rowis asserted truthy at line 253 and the unpacking returnsPrePostActionFramesetsRow(non-Optional). Drop the redundantand frameset_row is not None. - A
src/data_engine/processing/batch_processor.py:266—labeling_rowtyped non-Optional from_create_database_rows; trailingand labeling_row is not Noneis always True. Drop. - A
src/data_engine/processing/batch_processor.py:269— same dead guard onframeset_row. Simplify. - A
src/data_engine/processing/batch_processor.py:271— same dead guard onlabeling_row. Simplify. - B
src/data_engine/roi_data_miner/extract_env_data.py:937—downloader.get_frameset(frameset_id)annotated-> FrameSetbut realistically returnsNoneif id wasn't downloaded (surrounding code logs "not found after download"). WidenFrameSetDownloader.get_framesetreturn toFrameSet | None.
- B
src/deploy/launchers/lumi_ddp.py:816—content = f.read()is on a paramikoSFTPFile; paramiko's stubs saybytes, but in practice SFTP reads can yieldNoneon transient channel issues. Widen the local annotation or# pyright: ignore[reportUnnecessaryComparison](paramiko-typing gap).
- B
src/devops/terraform/common.py:34—GitRepo.working_diris typedstr | PathLike[str]by GitPython but is genuinelyOptional[PathLike]at runtime (e.g. bare repos), which is exactly why this code raisesInvalidGitRepositoryErrorand falls back to the CLI path. Keep the guard; suppress with# pyright: ignore[reportUnnecessaryComparison].
- A
src/frameset_tools/frameset_display.py:105—grid_size: tuple[int, int]is required (non-Optional) in__init__, soif grid_size is not None else entriesis unreachable. Drop the conditional, or change the parameter type totuple[int, int] | None = Noneif callers really do want the fallback.
- B
src/hyperstar/accelerate_patches.py:51— canonical single-assign init flag_PATCHED; annotate as_PATCHED: bool = False(not a true constant). - B
src/hyperstar/autopilot/eval_surrogate.py:108—LossCurveFeatures.convergence_pct: floatis too narrow; widen tofloat | Nonesince callers filter against None. - B
src/hyperstar/autopilot/eval_surrogate.py:226—_Xis an instance list attribute, not a constant; rename to_x_featuresor annotate without uppercase. - B
src/hyperstar/experiment_gui/transform.py:35— try/except import idiom; annotateHAS_DATEUTIL: bool = False(not a constant; both branches assign once). - B
src/hyperstar/experiments/_registry.py:66—_DISCOVEREDis a single-assign init flag; annotate as_DISCOVERED: bool = False. - B
src/hyperstar/experiments/lumi_remote_eval_smoke.py:33— module-level conditional init of_REQUEST_TOPIC; annotate_REQUEST_TOPIC: stronce or rename to non-uppercase. - B
src/hyperstar/experiments/lumi_remote_eval_smoke.py:34— same pattern:_RESULT_TOPICconditionally initialized in two branches. - A
src/hyperstar/hyperstar_loss_statistics.py:134—get_robot_namereturnsstrand raises on None; theis not Noneternary is dead, simplify torobot_name_val or ''. - A
src/hyperstar/hyperstar_loss_statistics.py:167— same dead defensive:get_robot_namealways returnsstror raises; drop theval if val is not None else ''walrus pattern. - C
src/hyperstar/hyperstar_loss_statistics.py:279—source_dfis typedpd.DataFramebut pandas-Any callers can pass None; keep guard, suppress with# pyright: ignore[reportUnnecessaryComparison]. - A
src/hyperstar/hyperstar_loss_statistics.py:507— sameget_robot_namepattern in lambda; the None branch is dead. - B
src/hyperstar/hyperstar_trainer.py:380—_optimizer: Optimizerdeclared non-Optional but property checks for None; widen field toOptimizer | None. - B
src/hyperstar/hyperstar_trainer.py:700—_training_loader: DataLoaderdeclared non-Optional butis Nonecheck exists; widen field. - B
src/hyperstar/init.py:450—GFX_BACKEND_INITIALIZEDis a single-assign init flag; annotate: bool = False. - B
src/hyperstar/intent_editor/auto_intent.py:246—JointTimeline.qpos: NDArray[np.float32]is too narrow; widen toNDArray[np.float32] | Nonesince dataset can yield empty timelines. - B
src/hyperstar/intent_editor/auto_intent.py:246— same:JointTimeline.actionshould beNDArray[np.float32] | None. - B
src/hyperstar/intent_editor/auto_intent.py:414— sameJointTimeline.qposwidening needed. - B
src/hyperstar/intent_editor/auto_intent.py:414— sameJointTimeline.actionwidening needed. - B
src/hyperstar/intent_editor/auto_intent.py:470—window_predict_intreturnsNDArray[np.int32]non-Optional but caller treats None as a possibility; widen return. - B
src/hyperstar/intent_editor/services/auto_trim_service.py:265—joint_timeline.actiontyped non-Optional; widenJointTimeline.action. - B
src/hyperstar/intent_editor/services/auto_trim_service.py:332— sameJointTimeline.actionwidening needed. - C
src/hyperstar/lerobot_datacleaner/datacleaner.py:596—gripper_spec.ctrl_index: inttyped strict but config-loaded data may have None; keep guard, suppress. - B
src/hyperstar/lerobot_support.py:73— draccus-parsedarm_config.calibration_diris typedstrbut is actuallystr | Nonefrom upstream; widen the upstream stub orcast/ignore at boundary. - C
src/hyperstar/lerobot_support_code/episode_writer.py:196—episode_data: dict[str, NDArray]typed non-Optional but caller dicts come from heterogeneous parquet/JSON; keep guard, suppress. - B
src/hyperstar/lerobot_support_code/patch_lerobot.py:61—_HYPERSTAR_TYPES_IMPORTEDis a single-assign import-success flag; annotate: bool = False. - C
src/hyperstar/lerobot_support_code/repair_dataset.py:129—dataset_fix.episode_data_indexfrom upstream LeRobot has stubdict[str, Tensor]but is actually nullable at runtime; keephasattr+is not Noneguard, suppress. - B
src/hyperstar/models/hyperflow.py:385—_prepare_qpos_history_embed(qpos_history: Tensor)is too narrow; caller passescomponents.qpos_history: Tensor | None. Widen param. - B
src/hyperstar/models/hyperflow.py:392— same:_prepare_action_history_embed(action_history: Tensor)should beTensor | None. - A
src/hyperstar/models/hyperflow.py:501—ip_scaleis set unconditionally fromcompute_intent_progress_loss_scale; theis not Noneternary is dead. - A
src/hyperstar/models/hyperflow.py:545—ip_scale_avg = ip_scale.mean(dim=-1)is unconditional;is not Nonecheck is dead. - A
src/hyperstar/models/hyperflow.py:557— same deadip_scale is not Noneguard; remove. - B
src/hyperstar/models/hyperformer.py:116—nn.ModuleDict.__getitem__returnsModulenon-Optional per stubs, butparams['wte']semantically may be a None placeholder; cast or use a typed lookup helper. - B
src/hyperstar/models/hyperformer.py:118— sameparams['drop'] is Noneagainstnn.ModuleDictstub limitation. - B
src/hyperstar/models/hyperformer.py:120— sameparams['ln_f'] is Noneagainstnn.ModuleDictstub limitation. - A
src/hyperstar/models/hyperformer.py:278— early returnif boundary_bias is None: return self.biasat line 267 makesboundary_bias is not Noneat 278 unreachable; delete the redundant inner check. - B
src/hyperstar/models/hyperformer_basic.py:216— local script-styleB = key_padding_mask.shape[0]shadows an outerB; rename tob(not a module constant). - A
src/hyperstar/models/hyperstar_model.py:1267—forward_state.diffusion_maskwas already narrowed byassert ... is not Noneat line 966; the secondis not Noneis dead. - B
src/hyperstar/robot_data/dataloader.py:506—_config: Configdeclared non-Optional but property defensively checks for None; widen field toConfig | None. - B
src/hyperstar/robot_data/dataloader.py:512— same_usage: HyperstarDatasetUsagefield needs| None. - B
src/hyperstar/robot_data/dataloader.py:656—seed: int = 42is non-Optional butseed is not Nonecheck exists; widen param toint | None = None. - B
src/hyperstar/robot_data/lerobot_dataset.py:665—hf_datasettyped non-Optional via the assignment branches; widen attribute since theis Nonebranch returns 0. - B
src/hyperstar/robot_data/lerobot_dataset.py:738—null_imagesfrom_create_null_images() -> NDArraynon-Optional; widen return or remove theis not Nonecheck. - A
src/hyperstar/robot_data/video_decoder/base_decoder.py:130—frameis reassigned fromget_fallback_frame()(returnsTensor) inside the priorif frame is Noneblock, soframe is not Noneis always true; simplify. - B
src/hyperstar/robot_data/video_decoder/pyav_decoder.py:188—cv2.imdecodestubs sayNDArraynon-Optional but it returns None on decode failure; widen the stub locally orcast— the guard is correct. - A
src/hyperstar/run.py:182—result = (cuda_device_order, [...])is a tuple literal that cannot be None; theis Nonecheck is genuinely dead. - A
src/hyperstar/task_definitions.py:147—RobotDefinition.robot_arms: list[str]typed non-Optional and code paths populate it; theis Nonecheck is dead.
- B
src/image/analysis/countcompartments.py:56—rgbparameter is annotatedNDArraybut callers pass results of cv2 decode / frameset accessors that can yieldNone. Widen the parameter toNDArray | None. - B
src/image/analysis/countcompartments.py:56(col 23) — same site fordepth; same fix. - B
src/image/analysis/helper.py:22—no_crate(depth: NDArray)defensively checksdepth is None; cv2.imdecode can return None. Widen todepth: NDArray | None. - B
src/image/analysis/pickpair.py:82—rgb0/rgb1annotatedNDArraybut the function is explicitly designed to handle missing channels (locals are pre-initialized toNone). Widen the four parameters indiff_images(...)toNDArray | None. - B
src/image/analysis/pickpair.py:82(col 29) — same site,rgb1clause; same fix. - B
src/image/analysis/pickpair.py:85— same function,depth0clause; same fix. - B
src/image/analysis/pickpair.py:85(col 31) — same function,depth1clause; same fix. - B
src/image/analysis/pickpair.py:88—[depth, color, structural, luminance]filterif channel is not None. Once parameters are widened to| None, this becomes valid. - B
src/image/utils.py:248—cv2.imdecodetruly returnsOptional[ndarray]at runtime; cv2 stub is too narrow. Either install/upgrade cv2 stubs, or annotatedecoded: NDArray | None = cv2.imdecode(...). Theis Noneguard must stay.
- C
src/infra/google/bigquery.py:258—bq_client_query(..., raise_on_error=False)is typedRowIterator | _EmptyRowIteratorper google-cloud-bigquery stubs, but withraise_on_error=Falsethe wrapper may legitimately swallow errors and return None at runtime. Keep the defensive check; add# pyright: ignore[reportUnnecessaryComparison]. (Could alternatively be A if the wrapper provably never returns None — verify the wrapper's real return type before fixing.) - B
src/infra/google/load_bq_from_excel.py:64—string_fieldsis locally rebound tostring_fields or []on line 36, so it'slist[Unknown]at this point. Either drop the redundant check, or change the parameter annotation tolist[str] | Noneand remove theor []coercion. - C
src/infra/google/metadataservice.py:109—bq_client_queryreturnsRowIterator | _EmptyRowIteratorper stubs, but this code clearly expects None to be possible (logs "did not contain barcode"). Keep the defensive guard; add# pyright: ignore[reportUnnecessaryComparison]. - A
src/infra/google/metadataservice.py:150—barcodewas just reassigned fromclean_barcode(None-checked at line 145 and returns early). At line 150barcode: strcannot be None. Drop theif barcode is not None else []. - A
src/infra/google/reporting/mail.py:81—service_account.Credentials.with_subjectis typed as returningCredentials(non-Optional) per google-auth stubs. Theif delegated_credentials is Nonebranch is dead. - A
src/infra/google/reporting/mail.py:88— same case forwith_scopes; dead branch. - C
src/infra/slack/main.py:219—bq_client_queryreturns non-Optional per stubs, but the[] if result is None else list(result)pattern is a one-liner safety net for the wrapper's real behavior. Keep +# pyright: ignore[reportUnnecessaryComparison]. - A
src/infra/tracing.py:63—print_resultis typedbool | Logger(default False); it cannot be None. Drop theFalse if print_result is None else print_resultline entirely.
- A
src/pap_rob/calibration_handler.py:48—ptislist[NDArray | None]; the list itself is never None. Drop thept is Noneclause; keeppt[0] is None. - A
src/pap_rob/calibration_handler.py:406—assert fs is not Nonetwo lines above already proves it; drop the redundantif fs is not Noneand unindent. - A
src/pap_rob/heuristical_pick_finder.py:181—margins: list[float]is non-Optional and always initialized to[]; drop theif self.margins is not Noneternary. - C
src/pap_rob/heuristical_pick_finder.py:357— model output dataclassconfidence: floatcould be missing in JSON-deserialized inference; suppress with# pyright: ignore[reportUnnecessaryComparison]. - B
src/pap_rob/lessons/nowaste_pick_and_place_lesson.py:238—world.robot_controlleris non-Optional in__init__but lesson teardown can run before init completes; widen toRobotController | None. - B
src/pap_rob/lessons/robot_calibration_lesson.py:102— same as above. - B
src/pap_rob/lessons/single_pick_lesson.py:76— same as above. - A
src/pap_rob/pap_rob_api.py:120—_running_mode = 'manual-picker'module-level constant, never reassigned; drop theis not Noneguard. - A
src/pap_rob/pap_rob_api.py:165—FrameSet.color_framesis a@propertyreturninglist[Frame], never None; dropor fs.color_frames is None. - A
src/pap_rob/pap_rob_api.py:176—depth_framesproperty returnslist[Frame]; replace with truthinessif not frameset.depth_frames. - A
src/pap_rob/pap_rob_api.py:200— same as above; dropor frameset.depth_frames is None. - B
src/pap_rob/pap_rob_api.py:206—world.get_station()raises on missing rather than returning None; widen its return toStation | Noneand remove the raise. - B
src/pap_rob/pap_rob_api.py:212—Camera.get_latest_frameset()annotatedFrameSetbut subclasses can return None at runtime; widen toFrameSet | None. - A
src/pap_rob/pap_rob_api.py:217—station.cameras: list[CalibratedCamera]sointrinsicsis non-Optional; drop the check. - A
src/pap_rob/pap_rob_api.py:217— same:extrinsicsisCameraExtrinsicsonCalibratedCamera. - C
src/pap_rob/pap_rob_api.py:313—cast(dict, request.get_json(silent=True))hides that flask json can be None; suppress. - C
src/pap_rob/pap_rob_api.py:314— same JSON cast pattern; suppress. - C
src/pap_rob/pap_rob_api.py:356—cast(RobotCellStatus, query_params.get('point', 'not-set'))widens default literal beyond the Literal type; suppress. - C
src/pap_rob/pap_rob_api.py:620—cast(dict, request.get_json(silent=True))hides runtime None; suppress. - B
src/pap_rob/pap_rob_api.py:696—Camera.get_latest_frameset()annotation too narrow; widen toFrameSet | None. - B
src/pap_rob/pap_rob_world.py:156—bqtf: BQToFileparameter is non-Optional butif bqtf is not Noneternary on next line proves caller can pass None; widen parameter. - B
src/pap_rob/pick_frameset_storage.py:130—Station.top_roiisCrop(sentinelCrop.NONE); the None-check suggests Optional intent; widen field toCrop | None. - A
src/pap_rob/pick_instruction_provider.py:430—assert operator_input.recommended_pick_point is not Noneat line 412 narrows it; dropp1 is not None and. - B
src/pap_rob/pickjob.py:197— comment says "Might have already cancelled this picking job"; widenrjobparameter toRobotPickJob | None. - A
src/pap_rob/pickjob.py:326—done_robot_jobs: list[RobotPickJob]elements are never None; dropif rj is not None. - B
src/pap_rob/place_env.py:115—Station.min_camera_depthis set to10.0default but legacy stations may not have it; widen tofloat | None. - A
src/pap_rob/placement/placement_provider.py:253—assert context.pick_env.used_tool is not Noneat line 222 narrows it; dropif tool is not None. - A
src/pap_rob/placement/virtual_placement_image_source.py:47— line 42 already returns whenfinalizing_robot_job is None, so reassignedrjobis non-None; drop. - B
src/pap_rob/station.py:139— TODO comment explicitly notes theextrinsicstype hint is wrong; fixCalibratedCamera.extrinsicsannotation back toCameraExtrinsics | None. - C
src/pap_rob/wms_interaction_api.py:130—cast(dict, request.json)hides runtime None body; suppress. - C
src/pap_rob/wms_interaction_api.py:257— same JSON cast pattern; suppress. - C
src/pap_rob/wms_interaction_api.py:297—cast(WMSPickJobStatus, ...)hides thatget_pickjob_statusreturnsWMSPickJobStatus | None; suppress (or remove the cast). - A
src/pap_rob/wms_interaction_api.py:328—abort_pickjob_requested_by_wmsreturnsbool, never None; drop theif res is Nonebranch (it's confused with truthiness — change toif not res). - C
src/pap_rob/wms_interaction_api.py:334— JSON cast pattern; suppress. - C
src/pap_rob/wms_interaction_api.py:351— JSON cast pattern; suppress. - C
src/pap_rob/wms_interaction_api.py:366— JSON cast pattern; suppress. - C
src/pap_rob/wms_interaction_api.py:381— JSON cast pattern; suppress. - B
src/pap_rob/wms_job_manager.py:283—PickJob.wms_job: WMSPickJobnon-Optional but the field guards suggest in-flight reset paths; widen toWMSPickJob | None. - B
src/pap_rob/wms_job_manager.py:307— same as above.
- C
src/robots/core/robot_controller.py:337—wms_job is Nonedefends a queue tuple-unpack against unexpectedNonepayloads. Producer types it asOrderline, but keeping a runtime guard on cross-thread queue contents is sensible; add# pyright: ignore[reportUnnecessaryComparison]. - B
src/robots/impl/hkm/opcua/opcua.py:473—_callbackis annotatedCallable[..., None](always set in__init__viacallback or opcua_client_value_changed), so the truthy guard is unreachable. Either drop the guard or annotate_callbackasCallable[..., None] | None. - B
src/robots/impl/hkm/opcua/opcua.py:706—opcua_config.password is not Noneruns againstOpcuaConfig.password: str(no Optional), but config loading may legitimately leave it unset for simulated servers. Widen tostr | None. - B
src/robots/impl/hkm/opcua/opcua_client.py:162— Same_callbackpattern asopcua.py:473. - B
src/robots/impl/hkm/opcua/opcua_recorder.py:351—_flush_change_queuereturnstuple[list, tuple[datetime, datetime]] | None; after the outer None check the innertimespan is Noneis unreachable per signature. Widen the return type. - C
src/robots/impl/hkm/opcua/opcua_recorder.py:500—dc.server_timestamptyped asdatetimebut originates frommonitored_item.Value.ServerTimestampover the OPCUA wire (can be missing). Keep guard, suppress. - B
src/robots/impl/kuka/ads/network/pyads_bugfixes.py:157— Classic PLC type-dispatch:plc_datatypeis either a ctypes class or atuple(nested struct def). Widen annotation. - B
src/robots/impl/kuka/ads/network/pyads_bugfixes.py:219— Sametype(plc_datatype) is tuplePLC dispatch. - B
src/robots/impl/kuka/ads/network/pyads_bugfixes.py:257— Same PLC dispatch pattern. - B
src/robots/impl/kuka/ads/network/pyads_bugfixes.py:310— Same PLC dispatch pattern. - B
src/robots/impl/kuka/ads/network/pyads_bugfixes.py:381— Same PLC dispatch pattern. - B
src/robots/impl/kuka/ads/network/pyads_bugfixes.py:434— Same PLC dispatch pattern. - B
src/robots/impl/kuka/kuka_robot_sim.py:317—self._handler.get_variable_by_name(var_path)is from an external pyads handler whose stub omits the legitimate "not found" case. The same method is checked truthy at line 342, confirming runtime can return falsy/None. Widen the handler return type.
- A
src/simulation/mujoco_sim/robotics.py:595—camera_fromis unpacked fromcamera(typedstr | list[str]); after the if/else either branch assigns astr. The trailingand camera_from is not Noneis unreachable. - B
src/simulation/roboception.py:599—RoboceptionData.camera_images: dict[str, NDArray[np.uint8]], but the dict is populated fromobservation.camera_framesand recording can omit a camera (yielding None per camera). Widen todict[str, NDArray[np.uint8] | None]. - A
src/simulation/sim_service/grpc_robot_cell_pool.py:106—code = _rpc_status_code(err)returnsStatusCode | None, but line 99 already raises whencode not in _RETRYABLE_STATUS_CODES(which includes None), so by line 106codeis provably non-None. Theif code is not None else 'unknown'fallback is dead.
- B
src/synq_api/main.py:558—ProductInfo.barcodeis annotatedstr, but the value originates from parsed Synq HTML/JSON and is missing in practice (the branch logs "No barcode found" and reports a deviation). Widen tostr | None. - B
src/synq_api/session.py:53—parsers.parse_for_workstationurlannotated-> strbut parses scraped HTML and can fail to find a match at runtime; the explicitraisehere is the recovery path. Widen the parser return type tostr | None. - B
src/synq_api/sics_api_calls.py:55—SICS_API_IPis an all-caps module-level mutable host config, not aFinal. AnnotateSICS_API_IP: str = ...(non-Final) or rename to lowercase / move into a small config object. - B
src/synq_api/sics_api_calls.py:55— Same as above forSICS_API_PORT. - B
src/synq_api/sics_api_calls.py:113—LAST_PREANNOUNCE_DICTis all-caps but is a mutable test/sim cache. Rename or annotate non-Final.
- B
src/utils/config/config.py:163—Config._app_name: stris annotated asstr, buthas_app_name()exists precisely because callers may constructConfigwithout one. Widen to_app_name: str | None. - B
src/utils/eventloop.py:42—_event_loopis initialized toNonein__new__(singleton-with-lazy-init pattern), but the attribute has no annotation so pyright infersAbstractEventLoopfrom the later assignment. Add an explicit_event_loop: AbstractEventLoop | None. - A
src/utils/memory.py:220—basestarts as the (non-None) input ndarray and is only reassigned tobase.basewhile that valueisinstance(_, np.ndarray), sobaseis provably an ndarray after the loop. Theif base is not None else 0tail is dead. - A
src/utils/memory.py:238— Same loop pattern as above.
- B
src/viz_server/product_classification.py:107—request.json(Flask) isAny | Noneupstream butcast(dict, request.json)strips theNonefrom the static view. Drop thecast(dict, ...)so the inferred type isAny | Noneand the guard is well-typed; alternatively# pyright: ignore[reportUnnecessaryComparison].
- C
tests/integration/data_engine/data_engine_db_test.py:704—bq_client.query().result()returnsRowIterator | _EmptyRowIteratorper BigQuery stubs, never None. Defensive guard against real BQ client behavior; keep, add# pyright: ignore[reportUnnecessaryComparison]. - B
tests/integration/hyperstar/helpers/history_usage_helpers.py:960— function signature istensor: Tensorbut body explicitly handles the None case (logs "None (no history)"). Change parameter totensor: Tensor | None. - C
tests/integration/hyperstar/joint_discretization/discretizer_test.py:241—dataset.task_definition.nametyped asstr; defensive null-guard. Real datasets may have empty/optional task names; keep, suppress. - B
tests/integration/hyperstar/model_integration/multi_iteration_sampling_test.py:97— local literal assignment narrowssampling_iter/action_predict_auto_stepstoLiteral. Annotate asintandint | None. - B
tests/integration/hyperstar/model_integration/multi_iteration_sampling_test.py:110(col 8) —sampling_iter = 1narrows toLiteral[1]; annotate asint. - B
tests/integration/hyperstar/model_integration/multi_iteration_sampling_test.py:110(col 31) —action_predict_auto_steps = 2narrows; annotate asint | None. - B
tests/integration/hyperstar/model_integration/multi_iteration_sampling_test.py:239— tuple-unpacked loop var narrows; type the test_cases list aslist[tuple[int, int | None]]. - B
tests/integration/hyperstar/model_integration/multi_iteration_sampling_test.py:264— same literal-narrowing pattern. - B
tests/integration/hyperstar/model_integration/training_prefill_test.py:100—training_prefill_mode = 5narrows toLiteral[5]; production type isint | Literal['none']. - B
tests/integration/hyperstar/model_integration/training_prefill_test.py:149(col 24) —sampling_iter = 1narrows; annotate asint. - B
tests/integration/hyperstar/model_integration/training_prefill_test.py:149(col 47) —training_prefill_mode = 5narrows; annotate asint | Literal['none']. - B
tests/integration/hyperstar/model_integration/training_prefill_test.py:184— same; annotate asint | Literal['none']. - B
tests/integration/hyperstar/model_integration/training_prefill_test.py:220—training_prefill_mode = prefill_countnarrows toint; annotate asint | Literal['none'].
- C
tests/unit/hyperstar/joint_discretization/discretizer_test.py:183—dataset.task_definition.nametyped asstr; defensive null-guard mirroring the integration test. Keep, suppress. - B
tests/unit/hyperstar/models/low_confidence_remasking_test.py:192—last_hf = torch.randn(2, 10, 32)narrows toTensor; the test deliberately mirrors productionlast_hf is not Nonecheck. Annotate aslast_hf: Tensor | None = torch.randn(...). - B
tests/unit/image/transformers/hue_transformer_test.py:57(col 5) —Creassigned at line 57 after first being bound at line 47 (semantically swaps axis ordering — latent code smell). Rename to lowercasec, h, w. - B
tests/unit/image/transformers/hue_transformer_test.py:57(col 8) — same forH. - B
tests/unit/image/transformers/hue_transformer_test.py:57(col 11) — same forW.
Of the 53 (A) sites, the largest concentrations are:
src/hyperstar/models/hyperflow.py— 3 deadip_scale is not Noneternaries (single fix).src/hyperstar/hyperstar_loss_statistics.py— 3 deadget_robot_nameternaries (single fix).src/data_engine/processing/batch_processor.py— 4 dead checks downstream of the sameassert frameset_row(one cluster).src/pap_rob/pap_rob_api.py— 6 dead checks against@propertyreturns and module-level constants.src/camera/femtomega/*— 3 deadnext_frameset()None-checks, plus 1 redundant_femtomegare-check.- Various individual already-narrowed-by-assert sites scattered across
src/pap_robandsrc/hyperstar.
Of the 111 (B) sites, the work consolidates into a small number of dataclass / signature widenings (see "High-leverage fixes" above). A concentrated 1–2-day effort on these clusters would resolve most of the (B) cohort.
Of the 23 (C) sites, 18 are either Flask request.json casts in pap_rob (one typed helper fix would handle 8 of them) or the bq_client_query wrapper return-type ambiguity (3 sites). Tactical # pyright: ignore with comments is the right call until the underlying wrappers/casts are reworked.