fix: ensure rm-filename header always includes file extension (#62)#65
Open
VladimirSenflokBrasty wants to merge 1 commit into
Open
Conversation
reMarkable Cloud started rejecting rm-filename header values without a
file extension on ~18 May 2026, returning HTTP 400 with
{"message":"unexpected 'rm-filename' http header"} for bare UUIDs.
This breaks all sync operations (rmapi ls, mkdir, put, mv, etc.) because
BuildTree / Mirror fail on the very first child blob fetch.
Several callers pass extensionless names through to the transport layer:
- tree.BuildTree:377 - provider.GetReader(e.Hash, e.DocumentID)
- blobdoc.Mirror:217 - r.GetReader(e.Hash, e.DocumentID)
- tree.BuildTree:363 - provider.GetReader(rootHash, "roothash") (legacy)
At the BlobStorage layer all such reads/writes represent doc-level
docSchema blobs, so default to the .docSchema suffix when the supplied
filename has no extension. Callers that already pass a full filename
(e.g. addExt(name, MetadataExt) → '<uuid>.metadata') are unaffected.
Verified via curl against internal.cloud.remarkable.com with a valid
bearer token:
rm-filename: <uuid> -> HTTP 400 (unexpected header)
rm-filename: <uuid>.docSchema -> HTTP 200 (real payload returned)
After the patch, rmapi ls / works on accounts that were locked out
since 18 May 2026.
Fixes ddvk#62
Signed-off-by: Vladimir Senflok <[email protected]>
|
#63 was already actively being worked on, I don't think it's a good idea to have competing PRs for the same issue. If you want to discuss implementation details, it's probably best to discuss it there. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Background
Since around 18 May 2026, the reMarkable Cloud API has been rejecting
rm-filenameHTTP header values that don't include a file extension. Every request gets back:This breaks all sync operations on existing accounts (
rmapi ls /already fails becauseBuildTree/Mirrorcan't fetch any child blob). See #62.Root cause
api/sync15/blobstorage.gopasses the caller-suppliedfilenamestraight through to therm-filenameheader. Several callers in the read path pass bare names without an extension:tree.go:377(BuildTree)e.DocumentID(bare UUID)blobdoc.go:217(BlobDoc.Mirror)e.DocumentID(bare UUID)tree.go:363(BuildTree, legacy path)"roothash"The Mirror happy path at
tree.go:278already wraps the name withaddExt("root", DocSchemaExt)and works fine — only the bare-name call sites trip the new server-side validation.I verified this directly with
curlagainstinternal.cloud.remarkable.com:HTTP 400 "unexpected 'rm-filename' http header"rm-filename: <uuid>HTTP 400 "unexpected 'rm-filename' http header"rm-filename: x-rm-filename: <uuid>(renamed)HTTP 400rm-filename: <uuid>.docSchemaHTTP 200 OK+ real payloadrm-filename: root.docSchemaHTTP 200 OKSo the server is happy as long as the value has any extension; the existing
addExt(...)callers all satisfy that already.Fix
Add a small
ensureExtensionhelper inBlobStoragethat appends.docSchemawhen the supplied filename has no., and wrap bothGetReaderandUploadBlobwith it. At this layer every extensionless name represents a doc-level docSchema blob (legacy"roothash", top-levele.DocumentIDfromparseIndexof the root index, etc.), so.docSchemais the right default. Callers that already pass a full filename (e.g.addExt(doc.DocumentID, archive.MetadataExt)→<uuid>.metadata) are untouched.This is intentionally a defensive single-point fallback rather than fixing each individual call site, because:
addExt(name, ext)helper intree.goas the canonical "I know the right extension" path,If you'd prefer the per-callsite variant, happy to redo it that way.
Verification
GOOS=linux GOARCH=arm64 CGO_ENABLED=0 go build, hot-swapped into a running container, ran the patched binary against an account that was 400'ing all morning.rmapi ls /,rmapi mkdir /test_$(date +%s),rmapi put some.pdf /Articles/all work again. End-to-end n8n → rmapi → reMarkable upload pipeline restored.Notes
WriteRootIndex(blobstorage.go:55in currentmaster) sendsrm-filename: "roothash"againstRootPut = /sync/v3/root, which is a different endpoint and wasn't exercised by my tests sincermapi lsis read-only. If that endpoint adopts the same validation, that header value should also gain a.docSchemasuffix — happy to extend the PR if you'd like.tree_test.goalready use full filenames in their fixtures, so they continue to pass; no test changes were required.Fixes #62.