Markdown ZIP import: attachment records created with name/key resolving to literal "null" and size=0 (files never written)
Is there an existing issue for this?
- I have searched the existing issues
This is not related to configuring Outline
- The issue is not related to self-hosting config
Current Behavior
When importing a Markdown ZIP archive (containing a nested folder structure of .md files plus a flat attachments/ folder) via Settings → Import → Markdown, a subset of the created Attachment records end up broken:
keyresolves touploads/{userId}/{attachmentId}/null(literal string"null"instead of the real filename)size = 0contentType = application/octet-stream(the mime-type fallback, not the real detected type)
The referencing document's text/content is otherwise completely correct — the page structure, hierarchy, and /api/attachments.redirect?id=... placeholders are all generated correctly, it's specifically the underlying Attachment row and the uploaded file bytes that are missing/corrupt.
The bug is deterministic and reproducible on the same input every time, but only affects a subset of the imported attachments per run (not all of them) — the exact subset seems to vary between otherwise-identical runs of the same ZIP.
Expected Behavior
Every attachment referenced in the imported Markdown should have a corresponding Attachment row with a correct key (matching the real uploaded filename), correct size, and correct contentType, and the underlying file should actually exist in storage — regardless of how many other attachments are being imported in the same batch.
Steps To Reproduce
- Prepare a Markdown ZIP with the following structure (matching what
[confluence-markdown-exporter](https://github.com/Spenhouet/confluence-markdown-exporter)produces, usingexport.attachment_href=relative):my-space/ page-a.md page-a/ child-page.md attachments/ 123456.jpg 123457.png ... - Reference the attachments from the
.mdfiles using relative Markdown image syntax, e.g.. - Import the ZIP via Settings → Import → Markdown (this matters — importing via a Collection's own "Import" button produces a flattened hierarchy and is a separate, likely related issue; only the Settings-level import correctly preserves hierarchy).
- Wait for the import to complete ("Import completed" in the worker logs).
- Inspect the
attachmentstable:→ a non-trivial subset of rows show this broken pattern.SELECT id, key, size, "contentType" FROM attachments WHERE size = 0 OR key LIKE '%/null';
This reproduces reliably even with a minimal test case: a single document referencing just 14 local image/PDF attachments, isolated in its own fresh Collection (no other imports/attachments present), still triggers the bug for 100% of that document's attachments on some runs. This rules out a pure scale/concurrency explanation, though the failure rate across a full space (~1100 attachments, ~10% broken) does suggest something batch/ordering-related rather than a per-file content issue.
Diagnostic notes (may help narrow it down)
The
import.scratch.manifest(captured live via polling before the import finishes and clears it) shows the correctnamefor the affected files at bootstrap time, e.g.:{ "id": "8c569749-b386-492f-a79a-ddfa8f33f145", "name": "6848573.jpg", "mimeType": "image/jpeg", "pathInZip": "attachments/6848573.jpg" }So the ZIP walk / manifest-building step (
processBootstrapinMarkdownAPIImportTask) is not at fault.The corruption therefore happens somewhere between manifest creation and the final
attachmentCreator()call inonAllTasksCompleted— i.e. in the completion/re-walk phase, or in whatever resolvesitem.nameat that point.We were able to fully work around the bug with a script that:
- Re-derives the correct original filename for each broken attachment ID by matching the document's rendered
/api/attachments.redirect?id=...placeholders (in order) against the original source Markdown'sattachments/<file>references (in order, for the same document, matched by title). - Copies the real file into the computed correct
keypath on local storage. - Runs
UPDATE attachments SET key = ..., size = ..., "contentType" = ....
This confirms the root files/manifest are correct throughout — repairing purely at the
attachmentstable + storage-file level, with no changes todocuments.text, is sufficient to fully restore the images.- Re-derives the correct original filename for each broken attachment ID by matching the document's rendered
Environment
- Outline: 1.10.1 (self-hosted, docker image outlinewiki/outline:latest)
- Deployment: Docker Compose, Postgres 16.15, Redis 7, FILE_STORAGE=local
- Browser: (not relevant — server-side import processing)Anything else?
Happy to provide the full repair script or more of the scratch/manifest polling data if useful for reproduction. This was found while migrating ~107 pages / ~520 attachments from a Confluence Data Center export (via confluence-markdown-exporter) into Outline Community Edition.
Expected Behavior
No response
Steps To Reproduce
No response
Environment
- Outline:
- Browser:Anything else?
No response
Source: outline/outline