Skip to content

fix: make /api/settings/status fast and truthful - #21

Open
luca-71 wants to merge 2 commits into
ethanplusai:mainfrom
luca-71:fix/settings-status-timeout
Open

fix: make /api/settings/status fast and truthful#21
luca-71 wants to merge 2 commits into
ethanplusai:mainfrom
luca-71:fix/settings-status-timeout

Conversation

@luca-71

@luca-71 luca-71 commented Aug 10, 2026

Copy link
Copy Markdown

The endpoint hung long enough to look broken, leaving the settings panel unable to load — and once it did answer, one of its three answers was wrong.

1. No overall time budget

Each individual osascript call was already bounded, but the endpoint total was not. Probing Calendar went through get_todays_events(), which on a cold cache fans out over every calendar in batches of two at 15s apiece. On a 10-calendar account that alone runs to 75s, before Mail (20s) and Notes (15s) are even reached. Prior to the user granting TCC permission, these block on a system dialog instead.

Added _probe_integration(), capping each probe at 8s and reporting a slow integration the same as a broken one, and run the three concurrently via asyncio.gather — they touch independent apps, so the endpoint now costs one budget rather than three.

2. The probes could not see failure

calendar_access and notes_access both answer an unreachable app with the same empty value they use for "nothing found". A probe that only caught raised exceptions therefore reported an app as healthy while its script was failing on every single call — which is exactly what was happening to Notes.

Calendar and Notes are now probed by listing containers (get_calendar_names() / get_note_folders()), with an empty result read as failure. Listing also sidesteps the expensive paths entirely.

This part matters on its own: capping the wait alone would have left the endpoint honest about its deadline but wrong about its result.

3. One unreadable note emptied the whole listing

get_recent_notes() read note properties unguarded, so a single note that Notes.app refuses to describe aborted the entire AppleScript and the function returned [].

Each note is now guarded individually, and the scan continues until count readable notes are collected — bounded, so a run of bad notes cannot walk an entire library.

The folder lookup gets its own guard. name of container of note turns out to fail with -1728 for every note on recent macOS: the container resolves, but as a bare item whose name is not exposed. So folder is empty in practice. Real folder names are only reachable by walking every note of folder, which scans the whole library on every call — not worth it while no caller reads the field. Guarding rather than dropping the lookup keeps it self-healing if Apple restores the property.

Measured

before after
Response time hung (>60s) 0.33s warm / 2.3s cold
calendar_accessible never (timeout) true
notes_accessible true (while failing on every call) true (and actually working)
get_recent_notes(10) [] 10 notes

Verified against a 10-calendar, 140-note account. The recurring Notes script failed: ... (-1728) warnings are gone from the server log.

Tests

pytest tests/ gives 35 passed, 8 failed — identical before and after these changes, so no regressions. The 8 pre-existing failures are unrelated: 7 need playwright install, and test_browse_action_keywords imports ACTION_KEYWORDS, a symbol that no longer exists in server.py.

🤖 Generated with Claude Code

Luca Trisiello and others added 2 commits August 10, 2026 16:55
The endpoint hung long enough to look broken. Each osascript call was
already bounded, but the endpoint's total was not: probing Calendar went
through get_todays_events(), which on a cold cache fans out over every
calendar in batches of two at 15s apiece. On a 10-calendar account that
alone runs to 75s, before Mail (20s) and Notes (15s) are even reached.
Prior to the user granting TCC permission, these block on a system
dialog instead.

Add _probe_integration(), which caps each probe at 8s and reports a slow
integration the same as a broken one, and run the three concurrently --
they touch independent apps, so the endpoint now costs one budget rather
than three.

Probe Calendar with get_calendar_names() instead: a single already
bounded osascript that answers the question a status endpoint actually
asks ("is Calendar reachable?"). Capping the wait alone would have left
the endpoint honest about its deadline but wrong about the result --
returning calendar_accessible: false for a Calendar that works fine.

Measured: response drops from hanging (>60s) to 0.28s warm / 2.3s cold,
and calendar_accessible now reports true.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
get_recent_notes() read note properties unguarded, so a single note that
Notes.app refuses to describe aborted the whole AppleScript and the
function returned []. On this library, note p627 did exactly that.

Guard each note individually and keep scanning until `count` readable
notes are collected, bounded so a run of bad notes can't walk an entire
library.

The folder lookup gets its own guard. `name of container of note` turns
out to fail with -1728 for every note on recent macOS -- the container
resolves, but as a bare `item` whose name is not exposed -- so "folder"
is empty in practice. Real folder names are only reachable by walking
`every note of folder`, which scans the whole library on every call; not
worth it while no caller reads the field. Guarding rather than dropping
the lookup keeps it self-healing if Apple restores the property.

Also probe Notes with get_note_folders() in /api/settings/status, and
treat an empty result as a failure. Both notes_access and calendar_access
answer an unreachable app with the same empty value they use for
"nothing found", so a probe that only caught raised exceptions reported
Notes as accessible while its script was failing on every call.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@luca-71 luca-71 changed the title fix: bound /api/settings/status probes so the settings panel loads fix: make /api/settings/status fast and truthful Aug 10, 2026
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.

1 participant