Skip to content

fix(observer): ocr_enabled flag — bypass screencapture popup storm#10

Merged
AVADSA25 merged 1 commit into
mainfrom
hotfix/observer-ocr-disable
May 2, 2026
Merged

fix(observer): ocr_enabled flag — bypass screencapture popup storm#10
AVADSA25 merged 1 commit into
mainfrom
hotfix/observer-ocr-disable

Conversation

@AVADSA25
Copy link
Copy Markdown
Owner

@AVADSA25 AVADSA25 commented May 2, 2026

Summary

User report (2026-05-02 morning): after merging Step 5 and starting codec-observer, the macOS Screen Recording permission popup fired "over a hundred times" — the user clicked Allow but it kept coming back.

Root cause

Two-part bug in _get_screenshot_ocr:

  1. macOS Allow is one-shot. Persistent grant requires the System Settings toggle, not the popup's Allow button. Clicking Allow on the popup only authorizes that single call.

  2. ThreadPoolExecutor.__exit__ blocks on shutdown. When the popup appears, screencapture's subprocess hangs waiting for the user. My code:

    with ThreadPoolExecutor(max_workers=1) as ex:
        future = ex.submit(_screencapture_and_ocr_blocking)
        return future.result(timeout=timeout_s)

    __exit__ calls shutdown(wait=True) — blocks until the thread finishes. The thread can't finish until the popup is dismissed. The "100ms timeout" actually waited ~5s (subprocess.run's own 2s+3s chain). And the Q5.1 retry triggered a SECOND popup. Per poll: ~2 popups + 5s blocking.

Fix

Add ocr_enabled config flag (default True — preserves Step 5 behavior on permission-granted installs). When false, poll() skips _get_screenshot_ocr entirely. Buffer still captures active_window + clipboard + recent_files; only OCR is bypassed.

// ~/.codec/config.json
"observer": {
    "ocr_enabled": false   // disable per-machine until permissions granted
}

Test plan

  • 3 new tests added in tests/test_observer.py:
    • test_ocr_enabled_false_skips_screenshot_call — sentinel-based, raises if _get_screenshot_ocr is called despite the flag
    • test_ocr_enabled_default_is_true — preserves existing behavior
    • test_ocr_enabled_true_calls_screenshot — sanity check for the default path
  • pytest tests/test_observer.py33/33 passing in 0.21s. Zero side effects (Apple Reminders=0, /tmp/codec_*.txt=0).

Deployment

This PR ships the code change. The user's runtime config has ALREADY been edited to ocr_enabled: false (atomic write, backup at ~/.codec/config.json.bak-1777736987). After merge:

pm2 restart codec-observer

Observer resumes polling without OCR. No popups possible.

Permanent fix (out of scope for this hotfix)

Two follow-ups for a future PR:

  1. Permissions docs in AGENTS.md: how to grant Screen Recording + Accessibility to node AND python3.13 via System Settings. Persistent grant survives restarts.
  2. Subprocess-leak fix: replace ThreadPoolExecutor + future.result(timeout=...) with subprocess.Popen + explicit proc.kill() on timeout. The current shutdown-wait pattern is the underlying root cause; the ocr_enabled flag is just the immediate off-switch.

🤖 Generated with Claude Code

User report (2026-05-02): after merging Step 5 and starting codec-observer,
the screencapture-based OCR fired the macOS Screen Recording permission
popup repeatedly — "over a hundred times" per user. Each click of "Allow"
on the popup grants only one-time access; the persistent grant is the
toggle in System Settings → Privacy & Security → Screen & System Audio
Recording.

Compounding bug in codec_observer._get_screenshot_ocr:

  with ThreadPoolExecutor(max_workers=1) as ex:
      future = ex.submit(_screencapture_and_ocr_blocking)
      return future.result(timeout=timeout_s)

The `with` exit calls executor.shutdown(wait=True) which BLOCKS until
the thread finishes. When the popup appears, screencapture's subprocess
hangs waiting for the user, and the thread can't finish. Result: my
"100ms timeout" actually waited up to 5 seconds (full subprocess.run
timeout chain on screencapture+osascript), and BOTH the first attempt
AND the Q5.1 retry trigger their own popup. Per poll: ~2 popups +
5s blocking until user dismisses.

Hotfix: add `ocr_enabled` config flag (default True — preserves Step 5
design behavior on permission-granted installs) that, when set false,
skips _get_screenshot_ocr entirely. Buffer still captures active_window
+ clipboard + recent_files; only OCR is bypassed. No screencapture call,
no popup risk.

  ~/.codec/config.json:
    "observer": {
      "ocr_enabled": false   ← user can flip to disable OCR per-machine
    }

3 new tests in tests/test_observer.py:
  - test_ocr_enabled_false_skips_screenshot_call (sentinel-based: raises
    if _get_screenshot_ocr is called when flag is false)
  - test_ocr_enabled_default_is_true (preserves existing behavior)
  - test_ocr_enabled_true_calls_screenshot (sanity check)

Result: 33/33 tests passing in 0.21s. Zero side effects.

Permanent fix for OCR users: grant Screen Recording AND Accessibility to
both `node` (PM2 parent) and `python3.13` (codec-observer process) in
System Settings → Privacy & Security. Then the toggle stays on across
restarts.

Deeper subprocess-leak issue (ThreadPoolExecutor.with-exit blocks on
shutdown wait=True) is NOT fixed in this commit — would require restructuring
the OCR call to use Popen with explicit kill on timeout, or
ThreadPoolExecutor.shutdown(wait=False, cancel_futures=True) (Python 3.9+).
Tracked for follow-up. The ocr_enabled flag is the safe immediate fix.
@AVADSA25 AVADSA25 merged commit 26e6add into main May 2, 2026
1 check passed
AVADSA25 pushed a commit that referenced this pull request May 2, 2026
…(Steps 5/6/7)

Phase 2 (Observer + Triggers + Shift Report) merged and production-stable:
- Step 5 (PR #9 824a52f) + hotfix PR #10 (26e6add) — `observer.ocr_enabled` flag
- Step 6 (PR #11 2d2ff3f) — Trigger System (matcher + cooldown + consent)
- Step 7 (PR #12 0e40687) — end-of-day shift report

Net: +91 passing tests (823/20/73), 0 new failures, 0 new skips.
Live audit proof captured: shift_report_started+_completed paired emits at
2026-05-02T18:49:40Z with shared cid=5f188e5485e5.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants