---
key: process.lock-ownership-enumerate-producers
title: 2026-07-21 — A lock/channel "no cross-release" verdict must enumerate every producer via grep (#542)
status: active
since: '2026-07-21'
supersedes: none
superseded-by: none
rule: Before trusting any "single owner / no double release / no cross-release" claim, grep the whole host project for every writer of that channel message (or acquirer of that lock) — the background scheduler/worker is the usual missing producer.
signals: 'cross-release · lock ownership · channel message producers · scheduler enqueues too · grep the host project · paths: n/a · issues: #542, #235, #267, #250'
mechanics: 'grep for every sender of the message type across the host project (e.g. all `Synchronize{X}Collections(` call sites), not just the controllers present in the PR diff. General form of "re-derive from a grep rather than enumerating from a list": see `sched.weighted-shuffle`.'
---

On #235/#267 a cold fork cleared a new `scan-collections` per-provider lock as sound — "only the
acquirer enqueues, so only its message triggers the scanner unlock" — having checked the three new
controllers in the diff and stopped there. Codex grepped: `SchedulerService` also enqueues those
messages periodically and unlocked, and `ScannerService`'s `finally` released the lock whenever held,
so a scheduled scan cross-releases an API scan's lock (the #250 class).

Corollary: the independent pass earns its keep even when the first reviewer returns MERGEABLE. The
first verdict was not careless — it was complete over the evidence it looked at, which is exactly the
failure mode a second, differently-scoped reviewer catches.
