fix(signal): preserve original filename for outbound attachments#38406
fix(signal): preserve original filename for outbound attachments#38406bricolage wants to merge 4 commits intoopenclaw:mainfrom
Conversation
When sending files via Signal, the recipient saw a UUID filename
(e.g. 8cccc293.pdf) instead of the original (e.g. report.pdf).
Root cause: saveMediaBuffer() generated a fresh UUID for every
outbound file, discarding the original name. signal-cli then used
this UUID as the displayed filename.
Fix:
- Pass media.fileName through resolveOutboundAttachmentFromUrl()
to saveMediaBuffer(), which already supports originalFilename and
stores files as {sanitized}---{uuid}.ext
- In the Signal send pipeline, use signal-cli's data URI format
(data:[mime];filename=[name];base64,[data]) so the display name
is the clean original filename extracted via extractOriginalFilename()
Fixes: Signal attachment filename always showing as UUID
Greptile SummaryThis PR fixes a UX issue where Signal recipients saw a UUID-based filename (e.g. The fix is split across two files:
Issues found:
Confidence Score: 4/5
Last reviewed commit: c1771d8 |
When sending files via Signal, the recipient saw a UUID filename (e.g. 8cccc293.pdf) instead of the original (e.g. report.pdf). Root cause: saveMediaBuffer() generated a UUID-based path for every outbound file. signal-cli used this basename as the displayed filename. Fix: in resolveOutboundAttachmentFromUrl(), if the loaded media has a known fileName, create a symlink in a uuid-named subdirectory with the sanitized original name as its basename. signal-cli (and any other consumer) then sees the correct filename via path.basename(). store.ts and all inbound callers are completely unaffected — the change is isolated to the outbound attachment resolver.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 9fbda2058d
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| const namedDir = path.join(path.dirname(saved.path), crypto.randomUUID()); | ||
| await fs.mkdir(namedDir, { recursive: true, mode: 0o700 }); |
There was a problem hiding this comment.
Keep outbound attachment aliases in cleanup-scanned paths
Creating a per-send subdirectory for the symlink alias causes media cleanup to miss these files: cleanOldMedia() only removes files one level deep (src/media/store.ts:95-120) and does not recurse into nested directories. With this change, each outbound attachment with a filename leaves behind a UUID directory and symlink that never get removed, so long-running Signal gateways will accumulate stale entries and inode usage over time.
Useful? React with 👍 / 👎.
resolveOutboundAttachmentFromUrl() creates a uuid-named subdirectory containing a symlink with the original filename so signal-cli sees the correct basename. cleanOldMedia() previously skipped subdirectories inside channel dirs (inbound/, outbound/, etc.), leaving these alias dirs and their symlinks to accumulate indefinitely. Changes to removeExpiredFilesInDir(): - Switch from fs.stat() to fs.lstat() to identify symlinks explicitly - Symlinks: expire based on target mtime (falls back to link mtime) - Subdirectories: recurse one level, then rmdir if empty after cleanup This keeps the media store tidy on long-running gateways.
|
Addressed in 5ebbe70. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 5ebbe7070c
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| const sanitized = trimmed.replace(/[^\p{L}\p{N}._-]+/gu, "_"); | ||
| return sanitized.replace(/_+/g, "_").replace(/^_|_$/g, "").slice(0, 200); |
There was a problem hiding this comment.
Reject dot segments when sanitizing attachment basenames
sanitizeBasename currently allows "." and ".." because dots are preserved and only underscores are trimmed. If loadWebMedia returns one of those names (for example from a remote Content-Disposition filename), path.join(namedDir, cleanName) resolves to the current/parent directory and fs.symlink throws (EEXIST/EISDIR), causing Signal send to fail even though the media file was already saved successfully. Treat these special basenames as invalid (or fall back to saved.path) before creating the alias.
Useful? React with 👍 / 👎.
sanitizeBasename() preserves dots, so a fileName of '.' or '..' would pass the truthy check and cause path.join(namedDir, '.') to resolve to the directory itself — making fs.symlink throw EISDIR/EEXIST and failing the send even though the file was saved successfully. Add an explicit check for '.' and '..' after sanitization and fall back to the UUID path when either is encountered.
|
Fixed in 673a367 — added explicit |
Problem
When an OpenClaw agent sends a file via Signal, the recipient sees a UUID filename (e.g.
8cccc293.pdf) instead of the original name (e.g.report.pdf).Root Cause
saveMediaBuffer()generates a UUID-based path for every outbound file. signal-cli uses the path's basename as the displayed filename — so recipients always see a UUID.Fix
Isolated entirely to
src/media/outbound-attachment.ts. No changes tostore.tsor any inbound callers.When
loadWebMedia()returns a knownfileName,resolveOutboundAttachmentFromUrl()now:saveMediaBuffer()(UUID path, unchanged)signal-cli (and any future consumer) sees
report.pdfas the basename. The real file stays at its UUID path — no collision risk, no inbound behavior change, no memory overhead.Scope
src/media/outbound-attachment.ts— symlink approach, sanitizeBasename helpersrc/signal/send.ts— reverted to originalattachments = [resolved.path]