Skip to content

Stop releasing the persisted grant the recent list depends on - #523

Merged
andiwand merged 1 commit into
mainfrom
landing/01-persisted-grants
Jul 27, 2026
Merged

Stop releasing the persisted grant the recent list depends on#523
andiwand merged 1 commit into
mainfrom
landing/01-persisted-grants

Conversation

@andiwand

@andiwand andiwand commented Jul 26, 2026

Copy link
Copy Markdown
Member

First of six, stacked. Base: main.

loadUri took a persistable uri permission and released it four lines later — before the load it had just queued onto LoaderService had run. Every uri written to the recently opened list was therefore unreadable on the next launch, so the one place that surfaced them handed back documents that would not open.

Grants are now reclaimed by reconciling what we hold against the recent list, instead of being released on close. Idempotent, no per-removal bookkeeping, and it mops up grants earlier versions leaked.

addRecentDocument's de-duplication was also a no-op: it read the json, called removeRecentDocument (which re-read, removed and saved), then wrote back the stale pre-removal array. Duplicates accumulated without bound.

Ordering, de-duplication and capping move into RecentDocumentList, free of android imports so plain JVM tests can cover them — the MimeTypeResolver pattern.

Verification

  • testProDebugUnitTest — 10 new cases over the logic the bug lived in
  • connectedProDebugAndroidTest green
  • On device: opened a document from Drive, force-stopped, relaunched, re-opened it from the list. That is the case that was broken.

The end-to-end fix is not assertable in the current harness — test fixtures come from the app's own FileProvider, which cannot grant persistable permissions.

loadUri took a persistable uri permission and released it four lines
later, before the load it had queued onto LoaderService had even run.
Every uri written to the recently opened list was therefore unreadable
on the next launch, so the one place that offered them - the last row of
the file manager chooser - handed back documents that would not open.

Grants are now reclaimed by reconciling the ones we hold against the
recent list, rather than released on close. That is idempotent, needs no
per-removal bookkeeping and mops up the grants earlier versions leaked.

The de-duplication in addRecentDocument was a no-op as well: it read the
json, called removeRecentDocument - which read the file again, removed
and saved - and then wrote back the stale pre-removal array. Duplicates
accumulated without bound. The ordering, de-duplication and capping
rules move into RecentDocumentList, which is free of android imports so
plain JVM tests can cover them, following MimeTypeResolver.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TiSZ4FeptMUsFjAuCewzQZ
@andiwand
andiwand marked this pull request as ready for review July 27, 2026 20:33
@andiwand
andiwand merged commit 2b246cc into main Jul 27, 2026
5 of 7 checks passed
@andiwand
andiwand deleted the landing/01-persisted-grants branch July 27, 2026 20:33

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 6f4b97859e

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

andiwand added a commit that referenced this pull request Jul 27, 2026
#523 stopped releasing the persisted grant on close and made prune() reconcile
what we hold against what the stored lists refer to. Between taking a grant and
the list referring to it there is a gap, though: loadUri() only queues the load,
so the entry is written much later, once MetadataLoader has read the document.

MainActivity starts prune() on its own thread from onCreate() and takes the
grant from onStart(), so a document opened from another app races them. Losing
that race releases the grant of the document currently being loaded: the load
itself still succeeds on the transient grant that came with the intent, the
entry lands in the recent list, and it is unreadable on the next launch - the
failure the reconciliation was written to fix, only through a narrower window.

Grants taken this process are therefore held until one of the stored lists names
them. The set is empty on a fresh process, so nothing leaks past a restart, and
prune() reads the permissions before the list and the list before the pending
set, so a grant taken while it runs cannot fall through both.

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
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