quaint-telephone-89068
05/27/2026, 1:46 PM$ pants --changed-since=HEAD~1 --changed-dependents=transitive lint
…
File ".../pants/init/specs_calculator.py", line 105, in calculate_specs
(changed_addresses,) = session.product_request(
File ".../pants/engine/internals/scheduler.py", line 523, in _raise_on_error
raise ExecutionError(
Exception message: 1 Exception encountered:
AttributeError: 'FrozenDict' object has no attribute 'replace'
NoneType: None
The same invocation without --changed-dependents=transitive succeeds. Plain pants lint :: succeeds.
Suspected cause
PR #22501 ("perf: Port frozendict to rust") replaced the Python FrozenDict with a Rust-backed implementation. Some rule on the ChangedAddresses → find_dependents codepath still calls .replace(...) on a FrozenDict instance, which no longer exists on the Rust port. Engine stack trace is suppressed (NoneType: None), so the exact callsite is opaque from a user repo.
Reproduction
Any non-trivial workspace with at least one transitive dependent:
pants --changed-since=HEAD~1 --changed-dependents=transitive lint
Workaround
Drop --changed-dependents=transitive and run lint/test over :: instead. Costs a full-tree run per CI invocation; viable with a remote cache (BuildBuddy / Buildfarm) but expensive on a cold runner.
Notes
• 2.31.1rc1 changelog only carries: jar-tool DEFLATED ZIP fix, sandboxer stdin/stdout fd fix, Trivy 0.69.2 bump. No frozendict change.
• No existing issue or PR mentioning FrozenDict.replace in repo search.
pantsbuild/pantsquaint-telephone-89068
06/19/2026, 8:57 PM