Summary
browser_screenshot commits screenshot bytes to the host attachment store with a hard-coded image/png media type, even when the underlying capture actually produces JPEG bytes. This corrupts the stored attachment reference and, on replay, dsh-attachment-local throws AttachmentError("Stored attachment metadata does not match its reference.", "ATTACHMENT_CORRUPT"). The error is then surfaced to the LLM adapter as code: "UNKNOWN", which the retry policy treats as retryable, causing the same assistant turn to fail and retry 18 times before giving up.
Environment
- Plugin:
@wxg-prc-cpg/browser-skill-dsh-plugin v0.1.1
- DeepSeek Harness (dsh) web profile
- Browser: Microsoft Edge 151.0.4129.101
bsk CLI v0.1.10
Reproduction
- In a dsh web session on a model route with image input, invoke
browser_screenshot.
- The plugin writes the capture bytes to
~/.dsh/attachments/v1/objects/… (content-addressed).
- Inspect the stored object — it is a JPEG (magic bytes
ff d8 ff db, file reports JPEG image data), but the reference recorded in the session declares mediaType: "image/png".
Concrete observed object (sha256 = filename, so integrity is intact):
| field |
reference value |
actual bytes |
mediaType |
image/png ❌ |
image/jpeg |
width |
2048 |
2048 ✅ |
height |
990 |
990 ✅ |
bytes |
147937 |
147937 ✅ |
Root cause
mediaType is hard-coded to "image/png" at two places in the dsh plugin, without ever probing the actual captured bytes:
packages/dsh-plugin-browserskill/src/image.ts:58
return await attachments.saveImage({ data, mediaType: "image/png", name });
packages/dsh-plugin-browserskill/src/tools.ts:845
image: {
attachmentId: String(ref.attachmentId),
mediaType: "image/png" as const,
bytes: ref.bytes,
width: ref.width,
height: ref.height,
...
}
The capture path itself also assumes PNG end-to-end:
apps/extension/src/tools/observation.ts:239 calls captureVisibleTab(windowId, { format: "png" }), and the protocol ScreenshotResult.format is documented as "Always "png" in v0.1" (crates/bsk-protocol/src/tools/observation.rs:208) — but the actual bytes can still be JPEG (e.g. captureVisibleTab returning JPEG on some Chromium/Edge builds), so format: "png" is an unverified claim, not a detected fact.
Because the plugin declares image/png but hands real JPEG bytes to attachments.saveImage, the stored reference carries a mediaType that contradicts the bytes. On the read path, dsh-attachment-local's readImageFile verifies the sha256 (passes — bytes unchanged), then re-probes metadata and detects image/jpeg !== image/png, throwing ATTACHMENT_CORRUPT.
Impact
- The screenshot attachment becomes permanently unreadable for the model.
- The failure is reported to the adapter as
code: "UNKNOWN" instead of ATTACHMENT_CORRUPT, so the retry policy (always mode) keeps retrying — 18 retries observed, ~27–30 s apart, ~8 minutes wasted — before the turn finally errors out.
Suggested fix
Stop trusting the declared "image/png". Either:
- Probe the actual bytes (e.g. with
sharp) and use the detected media type both when calling saveImage and when assembling the returned reference (ref.mediaType already carries the correct post-normalization type — use it instead of "image/png" as const); or
- If the capture source cannot guarantee PNG, drop the assumption at the source: detect/report the real format from
captureVisibleTab / CDP Page.captureScreenshot rather than hard-coding format: "png" in ScreenshotResult.
At minimum, tools.ts:845 should use ref.mediaType (the value saveImage actually returned) rather than re-declaring "image/png".
Related
Summary
browser_screenshotcommits screenshot bytes to the host attachment store with a hard-codedimage/pngmedia type, even when the underlying capture actually produces JPEG bytes. This corrupts the stored attachment reference and, on replay,dsh-attachment-localthrowsAttachmentError("Stored attachment metadata does not match its reference.", "ATTACHMENT_CORRUPT"). The error is then surfaced to the LLM adapter ascode: "UNKNOWN", which the retry policy treats as retryable, causing the same assistant turn to fail and retry 18 times before giving up.Environment
@wxg-prc-cpg/browser-skill-dsh-pluginv0.1.1bskCLI v0.1.10Reproduction
browser_screenshot.~/.dsh/attachments/v1/objects/…(content-addressed).ff d8 ff db,filereportsJPEG image data), but the reference recorded in the session declaresmediaType: "image/png".Concrete observed object (sha256 = filename, so integrity is intact):
mediaTypeimage/png❌image/jpegwidthheightbytesRoot cause
mediaTypeis hard-coded to"image/png"at two places in the dsh plugin, without ever probing the actual captured bytes:packages/dsh-plugin-browserskill/src/image.ts:58packages/dsh-plugin-browserskill/src/tools.ts:845The capture path itself also assumes PNG end-to-end:
apps/extension/src/tools/observation.ts:239callscaptureVisibleTab(windowId, { format: "png" }), and the protocolScreenshotResult.formatis documented as "Always"png"in v0.1" (crates/bsk-protocol/src/tools/observation.rs:208) — but the actual bytes can still be JPEG (e.g.captureVisibleTabreturning JPEG on some Chromium/Edge builds), soformat: "png"is an unverified claim, not a detected fact.Because the plugin declares
image/pngbut hands real JPEG bytes toattachments.saveImage, the stored reference carries amediaTypethat contradicts the bytes. On the read path,dsh-attachment-local'sreadImageFileverifies the sha256 (passes — bytes unchanged), then re-probes metadata and detectsimage/jpeg !== image/png, throwingATTACHMENT_CORRUPT.Impact
code: "UNKNOWN"instead ofATTACHMENT_CORRUPT, so the retry policy (alwaysmode) keeps retrying — 18 retries observed, ~27–30 s apart, ~8 minutes wasted — before the turn finally errors out.Suggested fix
Stop trusting the declared
"image/png". Either:sharp) and use the detected media type both when callingsaveImageand when assembling the returned reference (ref.mediaTypealready carries the correct post-normalization type — use it instead of"image/png" as const); orcaptureVisibleTab/ CDPPage.captureScreenshotrather than hard-codingformat: "png"inScreenshotResult.At minimum,
tools.ts:845should useref.mediaType(the valuesaveImageactually returned) rather than re-declaring"image/png".Related
captureVisibleTab/Page.captureScreenshotfailures, but that is a capture fails outright case; this report is the capture succeeds but media type is mis-declared, corrupting the stored attachment case.