Skip to content

gmail attachment: caching returns false hits for directories and unrelated files #223

@schickling

Description

@schickling

Problem

The attachment download caching in downloadAttachmentToPath has two issues that combine to produce false cache hits:

1. Cache accepts directories as valid cached files

os.Stat(outPath) succeeds on directories, and since expectedSize == -1 the check is just st.Size() > 0. A directory always has Size() > 0 (typically 64-512 bytes depending on filesystem), so it passes the cache check and returns cached: true.

2. Cache doesn't validate content — any non-empty file is a "hit"

When expectedSize == -1 (which is always the case for GmailAttachmentCmd.Run), the cache check is:

if st, err := os.Stat(outPath); err == nil && st.Size() > 0 {
    return outPath, true, st.Size(), nil
}

This means if any file exists at the output path (from a previous failed download, a different attachment, or even an unrelated file), it's returned as a cached result without any content validation.

Reproduction

# Write a small unrelated file to the output path
echo "not a PDF" > /tmp/invoice.pdf

# Try to download an attachment to the same path
gog gmail attachment <messageId> <attachmentId> --out /tmp/invoice.pdf
# Output: path=/tmp/invoice.pdf cached=true bytes=11
# Expected: should re-download the actual attachment (648KB PDF)

Suggested fix

In downloadAttachmentToPath:

  1. Check st.Mode().IsRegular() to reject directories
  2. When expectedSize > 0 is available, pass it through from the Gmail API metadata (the sizeEstimate field on the attachment) so the size check can reject wrong-sized files
  3. Consider adding a --no-cache / --force-download flag for programmatic usage where cache correctness matters

At minimum, checking for regular files would prevent the most confusing case (directories being treated as cached attachments).


Filed by an AI agent on behalf of @schickling

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions