Skip to content

Conversation

@VedantMadane
Copy link

Adds better error handling for SQLite OperationalError when the database file or directory is not writable. When the error message contains 'readonly' or 'unable to open', provides a helpful message suggesting the user check directory permissions. Also fixes a typo in the error message ('cannot not' to 'could not'). Closes #1676

Add specific error handling for permission-related SQLite errors. When the error message contains 'readonly' or 'unable to open', provide a helpful message suggesting the user check directory permissions. Also fix the typo 'cannot not' to 'could not'.

Closes beetbox#1676
@VedantMadane VedantMadane requested a review from a team as a code owner January 16, 2026 08:52
@github-actions
Copy link

Thank you for the PR! The changelog has not been updated, so here is a friendly reminder to check if you need to add an entry.

Copy link
Contributor

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

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

Hey - I've found 2 issues, and left some high level feedback:

  • The special-case readonly/unable to open branch currently assumes a directory permissions issue; consider broadening the message to mention both file and directory permissions (or phrasing it more generically) since these errors can also arise from file-level ACLs or missing paths.
  • When constructing db_dir = os.path.dirname(dbpath), it may be safer to normalize the path (e.g., via os.path.abspath) and handle the case where dbpath has no directory component, so that the resulting message about the directory being writable is always meaningful.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The special-case `readonly`/`unable to open` branch currently assumes a directory permissions issue; consider broadening the message to mention both file and directory permissions (or phrasing it more generically) since these errors can also arise from file-level ACLs or missing paths.
- When constructing `db_dir = os.path.dirname(dbpath)`, it may be safer to normalize the path (e.g., via `os.path.abspath`) and handle the case where `dbpath` has no directory component, so that the resulting message about the directory being writable is always meaningful.

## Individual Comments

### Comment 1
<location> `beets/ui/__init__.py:1513-1518` </location>
<code_context>
+        # Check for permission-related errors and provide a helpful message
+        error_str = str(db_error).lower()
+        dbpath_display = util.displayable_path(dbpath)
+        if "readonly" in error_str or "unable to open" in error_str:
+            db_dir = os.path.dirname(dbpath)
+            raise UserError(
+                f"database file {dbpath_display} could not be opened due to a "
+                f"permissions error. Please check that the directory "
+                f"{util.displayable_path(db_dir)} is writable."
+            )
         raise UserError(
</code_context>

<issue_to_address>
**issue (bug_risk):** The permission-specific branch may misdiagnose non-permission errors as permission issues.

Relying on the generic substring `"unable to open"` and mapping it directly to a permissions error is brittle. This message can also arise from non-permission issues (missing directory, malformed path, unavailable filesystem). Please either narrow the pattern to permission-specific cases, include the original `db_error` in the message for context, or soften the wording so it doesn’t claim permissions are the only cause.
</issue_to_address>

### Comment 2
<location> `beets/ui/__init__.py:1512-1522` </location>
<code_context>
+        dbpath_display = util.displayable_path(dbpath)
+        if "readonly" in error_str or "unable to open" in error_str:
+            db_dir = os.path.dirname(dbpath)
+            raise UserError(
+                f"database file {dbpath_display} could not be opened due to a "
+                f"permissions error. Please check that the directory "
+                f"{util.displayable_path(db_dir)} is writable."
+            )
         raise UserError(
</code_context>

<issue_to_address>
**suggestion:** The permission-focused message drops the original SQLite error details, reducing debuggability.

In this branch, the original `db_error` isn’t surfaced, unlike in the generic error path. Please include it here as well (e.g., by appending `f" (original error: {db_error})"`) so callers retain the underlying SQLite details for troubleshooting.

```suggestion
        dbpath_display = util.displayable_path(dbpath)
        if "readonly" in error_str or "unable to open" in error_str:
            db_dir = os.path.dirname(dbpath)
            raise UserError(
                f"database file {dbpath_display} could not be opened due to a "
                f"permissions error. Please check that the directory "
                f"{util.displayable_path(db_dir)} is writable "
                f"(original error: {db_error})."
            )
        raise UserError(
            f"database file {dbpath_display} could not be opened: {db_error}"
        )
```
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

@codecov
Copy link

codecov bot commented Jan 16, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 68.79%. Comparing base (4572a7d) to head (f1fa526).
✅ All tests successful. No failed tests found.

Additional details and impacted files
@@           Coverage Diff           @@
##           master    #6294   +/-   ##
=======================================
  Coverage   68.78%   68.79%           
=======================================
  Files         140      140           
  Lines       18619    18624    +5     
  Branches     3054     3055    +1     
=======================================
+ Hits        12807    12812    +5     
  Misses       5164     5164           
  Partials      648      648           
Files with missing lines Coverage Δ
beets/ui/__init__.py 83.01% <100.00%> (+0.12%) ⬆️
🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

- Changed wording from asserting permission error to suggesting it may be a permissions issue

- Include original db_error in the error message for debuggability

- Mention both file and directory in the error message
@VedantMadane
Copy link
Author

VedantMadane commented Jan 16, 2026

readonly errors only occur on write operations, not during database open. I've pushed a fix that removes the
readonly check and keeps only the unable to open check, which does occur here when the directory isn't writable and the database doesn't exist yet.

Also refined the error message to be more specific about the directory-not-writable-on-create scenario.

Add entry to changelog documenting improved error message when database
directory is not writable. The fix provides clearer guidance to users when
SQLite fails to open the database due to permission issues.

Closes beetbox#1676
Addresses review feedback requesting changelog update.
Add tests for both database error paths:
- Test 'unable to open' error path with directory permission message
- Test fallback error path for other database errors

This addresses the code coverage gap identified in PR review,
ensuring both error branches are covered by tests.
Resolved conflict in docs/changelog.rst by keeping both bug fix entries:
- Database permission error message improvement (PR beetbox#6294)
- ArtResizer OSError handling (from master)
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.

Better error message when directory containing database file is not writeable

2 participants