Skip to content

fix: an unreadable file must not read as an absent one - #1

Merged
benmfzen merged 1 commit into
mainfrom
fix/unreadable-is-not-absent
Jul 20, 2026
Merged

fix: an unreadable file must not read as an absent one#1
benmfzen merged 1 commit into
mainfrom
fix/unreadable-is-not-absent

Conversation

@benmfzen

Copy link
Copy Markdown
Owner

Path.exists() returns False when the OS denies access — macOS TCC, unix permissions, an unmounted share. Used as a guard before a read, it turns "I may not read this" into "this does not exist", and the caller falls back to defaults with nothing in the output to say so.

Found in the field (2026-07-20). A sibling deployment reported an empty review folder while nine finished videos sat in it: the scan was guarded by exists() and the process had lost Documents access. The reported 0 was indistinguishable from a genuinely empty folder — acting on it would have meant reporting "nothing to schedule" against a full queue.

The same guard sits in front of this repo's own state:

Path Silent consequence
plan.json unreadable plan looks like no plan → scheduler books against built-in defaults
damping ledger unreadable ledger looks like no history → series caps miscount
performance store unreadable store looks like no data → damped planner sees an empty record
config.json unreadable config looks like no config → silently runs on defaults

Change

Adds postpeer_pilot/safe_read.py. A read either returns content, returns None because the file genuinely is absent, or raises Unreadable. Callers that want a default now choose it explicitly, so the fallback is a decision in the code rather than an accident of the filesystem.

Applied to plan.active, plan.series_slots, plan.ledger_entries, plan.captions_by_post, perf.latest, the meta-token lookup in perf, config.load and config.api_key.

Tests

Five regression tests covering absent, present and chmod 000 paths — including that an unreadable plan and an unreadable config raise instead of quietly defaulting. Suite: 24 passed.

Behaviour for genuinely absent files is unchanged, so no caller needs updating.

🤖 Generated with Claude Code

`Path.exists()` returns False when the OS denies access — macOS TCC, unix
permissions, an unmounted share. Used as a guard before a read, it turns
"I may not read this" into "this does not exist" and the caller falls back
to defaults with nothing in the output to say so.

Found in the field on 2026-07-20: a sibling deployment reported an empty
review folder while nine finished videos sat in it, because the folder scan
was guarded by `exists()` and the process had lost Documents access. The
number 0 was indistinguishable from a real empty folder. Acting on it would
have meant reporting "nothing to schedule" against a full queue.

The same guard sits in front of this repo's own state: plan.json, the damping
ledger, the performance store and config.json. An unreadable plan looks like
no plan, so the scheduler books against built-in defaults; an unreadable store
looks like no history, so the damped planner sees an empty record. Both give
confident, wrong output.

Adds postpeer_pilot/safe_read.py: a read either returns content, returns None
because the file genuinely is absent, or raises Unreadable. Callers that want
a default now choose it explicitly, so the fallback is a decision in the code
rather than an accident of the filesystem.

Applied to plan.active, plan.series_slots, plan.ledger_entries,
plan.captions_by_post, perf.latest, perf meta-token lookup, config.load and
config.api_key. Regression tests cover absent, present and chmod-000 paths,
including that an unreadable plan or config raises instead of defaulting.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@benmfzen
benmfzen merged commit 501bad4 into main Jul 20, 2026
3 checks passed
@benmfzen
benmfzen deleted the fix/unreadable-is-not-absent branch July 20, 2026 13:34
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