I have searched the existing issues, both open and closed, to make sure this is not a duplicate report.
The bug
A library scan with *.* in exclusionPatterns soft-deletes the entire library.
globToSqlPattern() converts /path/*.* into SQL LIKE '/path/%.%'. SQL % matches /, so the pattern matches every file with a dot anywhere in its absolute path — /path/2020/x.jpg, /path/2021/sub/y.cr2, all of it.
detectOfflineExternalAssets() then sets isOffline=true, deletedAt=NOW() for every match in one transaction, before any disk crawl. Files stay on disk; assets go to trash. After 30 days they're gone.
Only one log line is emitted:
[LibraryService] N asset(s) out of N were offlined due to import paths and/or exclusion pattern(s) in library <id>
No warning. No error. No prompt.
Three occurrences on this deployment: 2025-11-21, 2026-04-04, 2026-04-30 — across v2.2.3, v2.3.1, v2.6.3. Most recent: 176,528 assets soft-deleted in a single second. Recovery is manual SQL.
#24005 reports the same code path with a different trigger (sequential **/YYYY/** removals). It was closed as duplicate of #14995, which is unrelated (trash-empty handling). This issue isolates the cleaner trigger: *.*.
The OS that Immich Server is running on
Ubuntu 24.04.4 LTS
Version of Immich Server
v2.6.3
Version of Immich Mobile App
N/a
Platform with the issue
Device make and model
Intel NUC5i5RYH
Your docker-compose.yml content
services:
immich-server:
container_name: immich_server
image: ghcr.io/immich-app/immich-server:${IMMICH_VERSION:-release}
volumes:
- ${UPLOAD_LOCATION}:/data
- /etc/localtime:/etc/localtime:ro
- /mnt/photos:/Library:ro
env_file:
- .env
ports:
- '2283:2283'
depends_on:
- redis
- database
redis:
container_name: immich_redis
image: docker.io/valkey/valkey:8
database:
container_name: immich_postgres
image: ghcr.io/immich-app/postgres:14-vectorchord0.4.3-pgvectors0.2.0
env_file:
- .env
volumes:
model-cache:
Your .env content
UPLOAD_LOCATION=/immich/library
DB_DATA_LOCATION=./postgres
TZ=Europe/Stockholm
IMMICH_VERSION=v2.6.3
DB_PASSWORD=<redacted>
DB_USERNAME=postgres
DB_DATABASE_NAME=immich
LOG_LEVEL=verbose
IMMICH_MACHINE_LEARNING_URL=http://machine-learning-host:3003
Reproduction steps
- Create an external library with
importPaths: ["/Library"] and assets in subdirectories (/Library/2020/x.jpg, /Library/2021/y.cr2).
- Run a library scan. All assets import.
- Add
/Library/*.* to exclusionPatterns to exclude loose root-level files.
- Trigger a library scan.
- Every asset is marked offline. UI shows an empty library.
To verify the SQL match:
SELECT COUNT(*) FROM asset WHERE "originalPath" LIKE '/Library/%.%';
-- Returns the full asset count, because % matches /.
-- On the affected deployment: 180,100 of 180,100.
Relevant log output
[LibraryService] 176528 asset(s) out of 181684 were offlined due to import paths and/or exclusion pattern(s) in library <library-uuid-redacted>
That is the entire output at `LOG_LEVEL=verbose`. No stack trace.
Database after the event:
SELECT date_trunc('hour', "deletedAt") AS deleted_hour, COUNT(*)
FROM asset
WHERE "libraryId" IS NOT NULL AND "deletedAt" IS NOT NULL
GROUP BY deleted_hour ORDER BY deleted_hour DESC LIMIT 5;
-- 2026-04-30 22:00:00+00 | 176528 <- bulk soft-delete in single second
Additional information
Code path (referenced in server/src/repositories/asset.repository.ts detectOfflineExternalAssets() ~line 1055)
.where((eb) =>
eb.or([
eb.not(eb.or(paths.map((path) => eb('originalPath', 'like', path)))),
eb.or(exclusions.map((path) => eb('originalPath', 'like', path))),
])
)
.set({ isOffline: true, deletedAt: new Date() })
The bulk-update runs before any disk verification. If exclusions matches everything, the whole library goes to trash atomically.
Glob → SQL LIKE semantics differ
Shell *.* matches foo.txt but not dir/foo.txt — * doesn't cross /.
SQL %.% matches both — % matches any character including /.
globToSqlPattern() doesn't translate this. The conversion silently widens the match.
Suggested fixes
- Refuse to soft-delete if a single scan would offline > X% of a library. Default 50%, configurable.
- Document that
*.* in exclusionPatterns uses SQL LIKE, not shell glob — it matches every file with a dot anywhere in the path.
- Fix
globToSqlPattern() so % doesn't cross /. Mirrors shell glob.
- Log which pattern matched how many assets at debug level.
Severity
Silent data-loss. One INFO log line, no warning, no prompt. Soft-deleted assets are gone after 30 days unless manually rescued. A scan should not be able to wipe a library without explicit confirmation. Three occurrences on this deployment across three Immich versions in six months.
Why this isn't a duplicate of #14995
#24005 was closed as a duplicate of #14995. This is NOT a duplicate. The two are different bugs:
I filed #24005 in January — no response. Three more incidents on this deployment since, most recent 2026-04-30 (176k assets soft-deleted on v2.6.3). Silent data-loss bug, still in production. You want paying customers, and I'll be happy to do so if bug reports are understood instead of closed, just because they are created with AI aid.
Available on request
- Verbose logs bracketing each recurrence
- DB exports for the affected library
- Exact
exclusionPatterns / importPaths that triggered each event
- Repro on a fresh test library
I have searched the existing issues, both open and closed, to make sure this is not a duplicate report.
The bug
A library scan with
*.*inexclusionPatternssoft-deletes the entire library.globToSqlPattern()converts/path/*.*into SQLLIKE '/path/%.%'. SQL%matches/, so the pattern matches every file with a dot anywhere in its absolute path —/path/2020/x.jpg,/path/2021/sub/y.cr2, all of it.detectOfflineExternalAssets()then setsisOffline=true, deletedAt=NOW()for every match in one transaction, before any disk crawl. Files stay on disk; assets go to trash. After 30 days they're gone.Only one log line is emitted:
No warning. No error. No prompt.
Three occurrences on this deployment: 2025-11-21, 2026-04-04, 2026-04-30 — across v2.2.3, v2.3.1, v2.6.3. Most recent: 176,528 assets soft-deleted in a single second. Recovery is manual SQL.
#24005 reports the same code path with a different trigger (sequential
**/YYYY/**removals). It was closed as duplicate of #14995, which is unrelated (trash-empty handling). This issue isolates the cleaner trigger:*.*.The OS that Immich Server is running on
Ubuntu 24.04.4 LTS
Version of Immich Server
v2.6.3
Version of Immich Mobile App
N/a
Platform with the issue
Device make and model
Intel NUC5i5RYH
Your docker-compose.yml content
Your .env content
Reproduction steps
importPaths: ["/Library"]and assets in subdirectories (/Library/2020/x.jpg,/Library/2021/y.cr2)./Library/*.*toexclusionPatternsto exclude loose root-level files.To verify the SQL match:
Relevant log output
Additional information
Code path (referenced in
server/src/repositories/asset.repository.tsdetectOfflineExternalAssets()~line 1055)The bulk-update runs before any disk verification. If
exclusionsmatches everything, the whole library goes to trash atomically.Glob → SQL LIKE semantics differ
Shell
*.*matchesfoo.txtbut notdir/foo.txt—*doesn't cross/.SQL
%.%matches both —%matches any character including/.globToSqlPattern()doesn't translate this. The conversion silently widens the match.Suggested fixes
*.*inexclusionPatternsuses SQL LIKE, not shell glob — it matches every file with a dot anywhere in the path.globToSqlPattern()so%doesn't cross/. Mirrors shell glob.Severity
Silent data-loss. One INFO log line, no warning, no prompt. Soft-deleted assets are gone after 30 days unless manually rescued. A scan should not be able to wipe a library without explicit confirmation. Three occurrences on this deployment across three Immich versions in six months.
Why this isn't a duplicate of #14995
#24005 was closed as a duplicate of #14995. This is NOT a duplicate. The two are different bugs:
detectOfflineExternalAssets()during library scan.I filed #24005 in January — no response. Three more incidents on this deployment since, most recent 2026-04-30 (176k assets soft-deleted on v2.6.3). Silent data-loss bug, still in production. You want paying customers, and I'll be happy to do so if bug reports are understood instead of closed, just because they are created with AI aid.
Available on request
exclusionPatterns/importPathsthat triggered each event