{"record":{"id":"189a59b658dc1a1d","repo":"gitbutlerapp/gitbutler","slug":"fatal-couldn-t-open-in-memory-url-path-err","errorCode":null,"errorMessage":"FATAL: Couldn't open in-memory URL: {path_err}","messagePattern":"FATAL: Couldn't open in-memory URL: (.+?)","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"crates/but-db/src/cache/mod.rs","lineNumber":70,"sourceCode":"\n/// Like [`run_migrations`], but made so that it cannot fail **and** opens the database either\n/// from `path`, removing broken ones on the fly, or from `:memory:` as final fallback,\n/// returning `(conn, actual_url)`.\n///\n/// # Panics\n///\n/// If in-memory databases can't be opened **and** migrations from zero don't work.\n/// Migrations are tested from zero, so that should be impossible.\nfn open_with_migrations_infallible<'p, 'm>(\n    path: &'p Path,\n    migrations: impl IntoIterator<Item = M<'m>> + Clone,\n) -> (rusqlite::Connection, &'p Path) {\n    let mem_url = \":memory:\".as_ref();\n    let res = rusqlite::Connection::open(path).map(|c| (c, path));\n    let (mut conn, mut path) = res\n        .or_else(|path_err| {\n            if path == mem_url {\n                panic!(\"FATAL: Couldn't open in-memory URL: {path_err}\")\n            }\n            tracing::warn!(\n                \"Failed to open cache database at '{path}' with {path_err}, will use memory DB instead\",\n                path = path.display()\n            );\n            rusqlite::Connection::open(mem_url)\n                .map(|c| (c, mem_url))\n                .map_err(|memory_err| {\n                    anyhow::Error::from(memory_err).context(path_err).context(format!(\n                        \"Couldn't open database either from {path} or in memory\",\n                        path = path.display()\n                    ))\n                })\n        })\n        .expect(\"FATAL: didn't expect to not be able to open an in-memory database at least\");\n\n    if let Err(err) = run_migrations(&mut conn, migrations.clone()) {\n        assert_ne!(","sourceCodeStart":52,"sourceCodeEnd":88,"githubUrl":"https://github.com/gitbutlerapp/gitbutler/blob/caf1f223d3cfb94488c9198ad34487c6006c648f/crates/but-db/src/cache/mod.rs#L52-L88","documentation":"`open_with_migrations_infallible` (but-db/src/cache/mod.rs:70) opens the cache database, falling back to `:memory:` when the on-disk path fails. The `FATAL` panic fires only when the requested path itself is `:memory:` and `rusqlite::Connection::open(\":memory:\")` fails — i.e. SQLite cannot even create an in-memory database. That indicates resource exhaustion or a broken SQLite build rather than a bad file path; for real paths, open failures degrade to the memory fallback or a contextual `anyhow` error instead.","triggerScenarios":"Passing \":memory:\" as the cache path while the process is out of memory, at the process/thread limit, or when SQLite's allocator fails; a rusqlite/SQLite build where in-memory VFS support is missing; ulimit or seccomp-sandboxed environments blocking SQLite's temp allocations.","commonSituations":"Memory-constrained CI containers or sandboxes running with the in-memory cache; exotic cross-compiled targets with a stripped-down SQLite; fork-bomb-style test parallelism exhausting fds/memory before the cache opens.","solutions":["Free memory or raise ulimits (fds, address space) in the environment, then retry startup.","Verify a plain probe `rusqlite::Connection::open(\":memory:\")` works in the same environment to isolate allocator/VFS issues.","If using a bundled/patched SQLite, rebuild with default features so the in-memory VFS is present.","Report the `memory_err`/`path_err` context from the anyhow chain — it names both the original path failure and the memory failure."],"exampleFix":null,"handlingStrategy":"fallback","validationCode":"// pre-flight: prove SQLite can create an in-memory database before startup\ntfn open_memory_probe() -> bool {\n    rusqlite::Connection::open(\":memory:\").is_ok()\n}\n\nif !open_memory_probe() {\n    // abort early with a clear message instead of the FATAL panic mid-startup\n    eprintln!(\"SQLite cannot open :memory: — check memory limits and the SQLite build\");\n    std::process::exit(1);\n}","typeGuard":null,"tryCatchPattern":"let cache = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {\n    open_with_migrations_infallible(Path::new(\":memory:\"), migrations)\n}));\nmatch cache {\n    Ok(v) => v,\n    Err(_) => {\n        // even :memory: failed: free resources, then retry once or exit with diagnostics\n        eprintln!(\"FATAL: in-memory cache unavailable; retrying after cleanup\");\n        open_with_migrations_infallible(Path::new(\":memory:\"), migrations)\n    }\n}","preventionTips":["Run memory-pressured workloads with ulimits sized for SQLite's page cache allocations.","Validate the SQLite/rusqlite build in new environments with a :memory: probe in smoke tests.","Monitor open connection counts so allocator failures surface as actionable alerts, not startup panics."],"tags":["rust","rusqlite","sqlite","in-memory-database","resource-exhaustion","cache"],"backgroundTag":"sqlite-open-failed","analyzedSha":"caf1f223d3cfb94488c9198ad34487c6006c648f","analyzedAt":"2026-08-20T07:55:40.983Z","schemaVersion":2},"datasetVersion":"2026-08-31T22:30:34.772Z"}