MuntashirAkon/AppManager · error · IOException

Could not fetch attributes for tree ${documentUri}

Error message

Could not fetch attributes for tree ${documentUri}

What it means

PathAttributesImpl.fromSaf queries the DocumentsProvider backing the given SAF DocumentFile for its metadata columns. If the returned cursor is empty (no first row), the provider has no record for this tree/document URI, so the library throws this IOException instead of returning partial attributes. It signals that the URI is unresolvable — usually deleted, revoked, or malformed.

Source

Thrown at app/src/main/java/io/github/muntashirakon/io/PathAttributesImpl.java:47

        return new PathAttributesImpl(f.getName(), file.getType(), f.lastModified(), f.lastAccess(), f.creationTime(),
                OsConstants.S_ISREG(mode), OsConstants.S_ISDIR(mode), OsConstants.S_ISLNK(mode), f.length());
    }

    @NonNull
    public static PathAttributesImpl fromVirtual(@NonNull VirtualDocumentFile file) {
        int mode = file.getMode();
        return new PathAttributesImpl(file.getName(), file.getType(), file.lastModified(), file.lastAccess(), file.creationTime(),
                OsConstants.S_ISREG(mode), OsConstants.S_ISDIR(mode), OsConstants.S_ISLNK(mode), file.length());
    }

    @NonNull
    public static PathAttributesImpl fromSaf(@NonNull Context context, @NonNull DocumentFile safDocumentFile)
            throws IOException {
        Uri documentUri = safDocumentFile.getUri();
        ContentResolver resolver = context.getContentResolver();
        try (Cursor c = resolver.query(documentUri, null, null, null, null)) {
            if (!c.moveToFirst()) {
                throw new IOException("Could not fetch attributes for tree " + documentUri);
            }
            String[] columns = c.getColumnNames();
            String name = null;
            String type = null;
            long lastModified = 0;
            long size = 0;
            for (int i = 0; i < columns.length; ++i) {
                switch (columns[i]) {
                    case DocumentsContract.Document.COLUMN_DISPLAY_NAME:
                        name = c.getString(i);
                        break;
                    case DocumentsContract.Document.COLUMN_MIME_TYPE:
                        type = c.getString(i);
                        break;
                    case DocumentsContract.Document.COLUMN_LAST_MODIFIED:
                        lastModified = c.getLong(i);
                        break;
                    case DocumentsContract.Document.COLUMN_SIZE:

View on GitHub (pinned to 0152f468fc)

Solutions

  1. Re-acquire the tree/document via the Storage Access Framework picker (ACTION_OPEN_DOCUMENT_TREE / ACTION_OPEN_DOCUMENT) and rebuild the DocumentFile
  2. Check that you still hold the URI permission (context.contentResolver.persistedUriPermissions) and re-take it if revoked
  3. Verify the document still exists via DocumentFile.exists() before querying attributes
  4. Wrap the call in try-catch for IOException and treat it as a 'missing document' case rather than retrying with the same stale URI

Example fix

// before
PathAttributesImpl attrs = PathAttributesImpl.fromSaf(context, staleDocFile);
// after
if (!staleDocFile.exists() || !hasUriPermission(context, staleDocFile.getUri())) {
    staleDocFile = rePickTreeViaSaf(); // ACTION_OPEN_DOCUMENT_TREE
}
PathAttributesImpl attrs = PathAttributesImpl.fromSaf(context, staleDocFile);
Defensive patterns

Strategy: try-catch

Validate before calling

if (docFile == null || !docFile.exists() || !hasPersistedPermission(context, docFile.getUri())) { reAcquireTree(); }

Type guard

boolean isResolvableTree(Context ctx, DocumentFile f) {
    return f != null && f.getUri() != null
        && ctx.getContentResolver().getPersistedUriPermissions().stream()
            .anyMatch(p -> p.getUri().equals(f.getUri()));
}

Try / catch

try {
    PathAttributesImpl attrs = PathAttributesImpl.fromSaf(context, docFile);
} catch (IOException e) {
    if (e.getMessage() != null && e.getMessage().startsWith("Could not fetch attributes for tree")) {
        docFile = rePickTreeViaSaf(); // URI dead or revoked
    } else throw e;
}

Prevention

When it happens

Trigger: Calling Path.getAttributes()/fromSaf with a DocumentFile whose tree URI no longer resolves: the document was deleted or moved, the persisted URI permission was revoked (e.g. after app data clear or reboot on some devices), or the provider returned a valid-but-empty cursor.

Common situations: Persisted ACTION_OPEN_DOCUMENT_TREE URIs pointing at storage roots that were re-mounted or wiped; third-party DocumentsProviders (cloud providers) being offline; testing with a fabricated Uri that was never returned by the SAF picker.

Understand the failure class

Background: EmptyResultError / "no results found": when an API or scraper succeeds but returns zero rows — this error's family across 9 libraries.

Related errors


AI-assisted analysis of MuntashirAkon/AppManager@0152f468fc (2026-09-12). Data as JSON: /api/errors/d27bcc3d6d466f92. Report an issue: GitHub.