fix(cli): do not block watch/search on undetermined Contacts - #238
Conversation
Headless imsg watch and imsg search called ContactResolver.create() with the default requestIfNeeded policy, so a .notDetermined Contacts authorization could hang on the TCC prompt. Share the RPC TTY rule (skipIfNotDetermined when stdin is not a TTY). send still prompts. Signed-off-by: Sebastien Tardif <sebtardif@ncf.ca>
|
🦞👀 Pull request received. I will update this pull request when review starts. |
|
Codex review: needs maintainer review before merge. Reviewed August 15, 2026, 10:43 PM ET / August 16, 2026, 02:43 UTC. ClawSweeper reviewWhat this changesThe branch centralizes stdin TTY detection and makes headless Merge readinessThis PR remains necessary: current main still requests Contacts access before Priority: P2 Review scores
Verification
How this fits togetherThe imsg CLI reads and watches the local Messages database, then optionally resolves handle display names through Contacts. Contacts authorization is selected during command startup, before search results or a watch stream can be emitted. flowchart LR
A[CLI stdin] --> B[TTY detection]
B --> C[Contacts access policy]
C --> D[Contact resolver]
D --> E[Search or watch command]
E --> F[Message output or stream]
Before merge
Agent review detailsSecurityNone. Review metrics
Merge-risk optionsMaintainer options:
Technical reviewBest possible solution: Land the shared TTY policy so noninteractive read automation starts reliably while interactive use and explicit name-resolution flows keep their prompt-capable behavior. Do we have a high-confidence way to reproduce the issue? Yes, source-reproducible: on macOS with Contacts authorization undetermined and non-TTY stdin, current main calls the default prompt-capable resolver before Is this the best way to solve the issue? Yes. Reusing the merged RPC TTY rule at the shared policy layer is the narrowest maintainable repair, and it leaves interactive prompting and AGENTS.md: found and applied where relevant. Codex review notes: model internal, reasoning high; reviewed against 268912be7189. LabelsLabel justifications:
EvidenceWhat I checked:
Likely related people:
Rating scale
Overall follows the weaker of proof and patch quality. Workflow
History |
CHANGELOG.md is release-owned; keep the user-visible context in the PR body. Signed-off-by: Sebastien Tardif <sebtardif@ncf.ca>
|
@clawsweeper re-review
Dropped the Unreleased CHANGELOG.md edit on 63f566f. User-visible context stays in the PR body. |
|
🦞🧹 I asked ClawSweeper to review this item again. Re-review progress:
|
What Problem This Solves
Merged #187 (issue #186) fixed headless
imsg rpcso Contacts.notDetermineddoes not block forever. CLIwatchandsearchstill calledContactResolver.create()with the default.requestIfNeeded:When Contacts authorization is still
.notDetermined, that awaitsCNContactStore.requestAccess. In a headless (non-TTY) process the prompt can stay pending, soimsg watchandimsg searchhang before they emit output.chatsandhistoryalready use.skipIfNotDetermined(#136).imsg sendstill prompts, because name targets need Contacts.Evidence
Prior art (same repo)
ContactsAccessPolicy.skipIfNotDeterminedforimsg chatsandimsg history.imsg rpc(stdinnot a TTY -> skip; interactive TTY -> request).watchandsearchwere left on the default.ContactResolver.create(accessPolicy: .skipIfNotDetermined)already returnsNoOpContactResolver(contactsUnavailable: true)without callingrequestAccess.Fix
The TTY rule now lives once on
ContactsAccessPolicy.forStdin(isTTY:). RPC, watch, and search factories all use it.imsg sendis unchanged.Live headless search (after fix)
Environment: macOS 26.6.1 (Build 25G76), arm64, Contacts authorization notDetermined (raw
0), patched binary at commit545aa3d, stdin from/dev/null(not a TTY). Fixture Messages DB via--dbso the command reachesContactResolver.create.Search returned a fixture hit in under a second while Contacts stayed undetermined (no prompt hang). Headless
imsg watchon the same DB stayed in its event loop (killed after 2.013s), so Contacts did not block startup.Real behavior proof
Behavior or issue addressed: Headless
imsg watchandimsg searchmust not block on an undetermined Contacts permission prompt. Interactive TTY still uses the prompt-capable path.imsg sendstill prompts.Real environment tested: macOS 26.6.1 (Build 25G76), arm64, Contacts authorization status
notDetermined(raw 0) on the host. Patched binary545aa3dat/tmp/oc-impl-imsg-contacts/bin/imsg. Headless non-TTY stdin (/dev/null). Fixture chat.db so FDA is not required and the command still runsContactResolver.create.Exact steps or command run after this patch:
Evidence after fix: terminal output from the headless search session above:
imsg search --query helloprinted the fixture message and exited 0 in 0.755s while Contacts stayed undetermined. Headlessimsg watchon the same DB was still running after 2.013s (Contacts did not block startup).Observed result after fix: watch and search no longer wait on
requestAccesswhen stdin is not a TTY and Contacts is undetermined. Name enrichment remains optional until Contacts is already authorized (fixture used the raw handle+15555550100).What was not tested: Live authorized-Contacts path on this host (authorization is notDetermined, not authorized). Interactive TTY prompt path was not exercised here (policy keeps
.requestIfNeededfor TTY).imsg sendwas not changed. Real LaunchAgent plist install was not used; proof used equivalent non-TTY stdin.Notes
ContactsAccessPolicy.forStdin(isTTY:)so the TTY rule is not copied in three places