diff --git libpkg/pkgdb.c libpkg/pkgdb.c
index 1720a851c..db4c16a7a 100644
--- libpkg/pkgdb.c
+++ libpkg/pkgdb.c
@@ -1049,6 +1049,32 @@ pkgdb_open_all(struct pkgdb **db_p, pkgdb_t type, const char *reponame)
 	vec_free(&r);
 	return (ret);
 }
+
+/*
+ * Probe a freshly opened read-only connection with a trivial schema
+ * read. For a WAL-mode database this is what triggers the creation or
+ * opening of the -wal/-shm sidecar files; if they cannot be accessed
+ * the prepare fails (SQLITE_READONLY or SQLITE_CANTOPEN) even though
+ * sqlite3_open_v2() itself succeeded.
+ */
+static bool
+pkgdb_ro_probe_ok(sqlite3 *sqlite)
+{
+	sqlite3_stmt	*stmt = NULL;
+	int		 rc;
+	bool		 ok;
+
+	if (sqlite3_prepare_v2(sqlite, "SELECT 1 FROM sqlite_master LIMIT 1",
+	    -1, &stmt, NULL) != SQLITE_OK)
+		return (false);
+
+	rc = sqlite3_step(stmt);
+	/* ROW or DONE means the query ran against the schema. */
+	ok = (rc == SQLITE_ROW || rc == SQLITE_DONE);
+	sqlite3_finalize(stmt);
+	return (ok);
+}
+
 int
 pkgdb_open_all2(struct pkgdb **db_p, pkgdb_t type, c_charv_t *reponames)
 {
@@ -1108,11 +1134,19 @@ retry:
 		 * WAL concurrency (shared memory locking) and avoids
 		 * seeing partially-checkpointed pages.
 		 *
-		 * Fall back to immutable=1 only when the WAL sidecar
-		 * files (-wal/-shm) are not accessible, which means we
-		 * cannot participate in the WAL protocol at all.
-		 * immutable=1 bypasses WAL/SHM entirely and reads
-		 * directly from the main database file.
+		 * A read-only connection to a WAL-mode database still
+		 * needs the -wal/-shm sidecar files: it creates them on
+		 * first use if they are missing, and opens the -shm to
+		 * take part in the WAL protocol. sqlite3_open_v2() reports
+		 * SQLITE_OK even when this is impossible, so every query
+		 * then fails with SQLITE_READONLY or SQLITE_CANTOPEN. This
+		 * happens in particular for non-root users reading a
+		 * root-owned database whose sidecars were removed after
+		 * the writer closed.
+		 *
+		 * Probe the connection with a schema read and, on failure,
+		 * reopen in immutable mode, which bypasses WAL/SHM entirely
+		 * and reads directly from the main database file.
 		 */
 		if (!create && (type == PKGDB_DEFAULT_READONLY ||
 		    faccessat(dbdirfd, "local.sqlite",
@@ -1129,6 +1163,13 @@ retry:
 				    &db->sqlite,
 				    SQLITE_OPEN_READONLY | SQLITE_OPEN_URI,
 				    NULL);
+			} else if (!pkgdb_ro_probe_ok(db->sqlite)) {
+				sqlite3_close(db->sqlite);
+				ret = sqlite3_open_v2(
+				    "file:/local.sqlite?immutable=1",
+				    &db->sqlite,
+				    SQLITE_OPEN_READONLY | SQLITE_OPEN_URI,
+				    NULL);
 			}
 		} else {
 			ret = sqlite3_open("/local.sqlite", &db->sqlite);
