Summary
On v2, inbound WhatsApp attachments are downloaded to a host directory that is not mounted into the agent container, and the agent is handed a /workspace/attachments/... path that doesn't exist inside the container. As a result the agent cannot open images/documents/audio a user sends — it reports the file as missing.
Environment
- NanoClaw v2.0.76
- WhatsApp channel installed via
/add-whatsapp (from the channels branch), Baileys 7.0.0-rc.9
- Runtime: Docker, macOS host
Steps to reproduce
- Install the WhatsApp channel and wire it to an agent group.
- Send the bot an image (or PDF / voice note) in a DM.
- Ask the agent to describe / read the attachment.
Expected
The agent can read the file (e.g. via the Read tool / multimodal), since v2 stages inbound attachments into the session inbox that's mounted at /workspace.
Actual
The agent reports it can't find the file. The attachment never reaches a path inside the container.
Root cause
downloadInboundMedia() in src/channels/whatsapp.ts:
- writes the downloaded buffer to
path.join(DATA_DIR, 'attachments'), and
- returns the attachment as
{ type, name, localPath: 'attachments/<file>' } (no base64 data).
Two problems compound:
DATA_DIR/attachments is never mounted. The container's /workspace is mounted from the per-session directory (sessionDir(...)), not from DATA_DIR. So the /workspace/attachments/... path the formatter surfaces to the agent doesn't exist in the container.
extractAttachmentFiles() in src/session-manager.ts only stages attachments that carry base64 data (if (typeof att.data !== 'string') continue;). Because the WhatsApp adapter emits a localPath and no data, the staging step skips it entirely — so the media is never copied into the mounted inbox/<messageId>/.
Net: the adapter bypasses v2's own attachment-staging contract (base64 data → host stages into the session inbox → rewrites a valid in-container localPath), which the tests in src/host-core.test.ts exercise with { name, data, size }.
Proposed fix
Have downloadInboundMedia() return base64 data and let the host's extractAttachmentFiles() do the staging (which also re-sanitizes the filename and writes safely with the wx flag). No pre-write to DATA_DIR needed:
// src/channels/whatsapp.ts — downloadInboundMedia()
const buffer = (await downloadMediaMessage(msg, 'buffer', {})) as Buffer;
const rawFilename = normalized[key].fileName;
const fallback = `${type}-${Date.now()}${ext}`;
const filename = isSafeAttachmentName(rawFilename) ? rawFilename : fallback;
results.push({
type,
name: filename,
data: buffer.toString('base64'),
size: buffer.length,
});
(Drop the DATA_DIR/attachments mkdirSync / writeFileSync / localPath branch; the DATA_DIR import then becomes unused.)
Side benefits
- Routes through the existing inbox staging, which is per-session (proper isolation — no shared
DATA_DIR/attachments visible across agent groups) and already hardened against symlink / path-traversal attacks.
- Brings the WhatsApp adapter in line with the documented attachment contract and the
host-core tests.
Verified locally: with this change, inbound images / PDFs are staged into inbox/<messageId>/ and the agent reads them normally.
Summary
On v2, inbound WhatsApp attachments are downloaded to a host directory that is not mounted into the agent container, and the agent is handed a
/workspace/attachments/...path that doesn't exist inside the container. As a result the agent cannot open images/documents/audio a user sends — it reports the file as missing.Environment
/add-whatsapp(from thechannelsbranch), Baileys7.0.0-rc.9Steps to reproduce
Expected
The agent can read the file (e.g. via the
Readtool / multimodal), since v2 stages inbound attachments into the session inbox that's mounted at/workspace.Actual
The agent reports it can't find the file. The attachment never reaches a path inside the container.
Root cause
downloadInboundMedia()insrc/channels/whatsapp.ts:path.join(DATA_DIR, 'attachments'), and{ type, name, localPath: 'attachments/<file>' }(no base64data).Two problems compound:
DATA_DIR/attachmentsis never mounted. The container's/workspaceis mounted from the per-session directory (sessionDir(...)), not fromDATA_DIR. So the/workspace/attachments/...path the formatter surfaces to the agent doesn't exist in the container.extractAttachmentFiles()insrc/session-manager.tsonly stages attachments that carry base64data(if (typeof att.data !== 'string') continue;). Because the WhatsApp adapter emits alocalPathand nodata, the staging step skips it entirely — so the media is never copied into the mountedinbox/<messageId>/.Net: the adapter bypasses v2's own attachment-staging contract (base64
data→ host stages into the session inbox → rewrites a valid in-containerlocalPath), which the tests insrc/host-core.test.tsexercise with{ name, data, size }.Proposed fix
Have
downloadInboundMedia()return base64dataand let the host'sextractAttachmentFiles()do the staging (which also re-sanitizes the filename and writes safely with thewxflag). No pre-write toDATA_DIRneeded:(Drop the
DATA_DIR/attachmentsmkdirSync/writeFileSync/localPathbranch; theDATA_DIRimport then becomes unused.)Side benefits
DATA_DIR/attachmentsvisible across agent groups) and already hardened against symlink / path-traversal attacks.host-coretests.Verified locally: with this change, inbound images / PDFs are staged into
inbox/<messageId>/and the agent reads them normally.