Skip to content

fix(host-transfer): emit Content-Length and X-Transfer-SHA256 on GET content#29449

Merged
noanflaherty merged 1 commit into
mainfrom
credence/transfer-content-response-headers
May 3, 2026
Merged

fix(host-transfer): emit Content-Length and X-Transfer-SHA256 on GET content#29449
noanflaherty merged 1 commit into
mainfrom
credence/transfer-content-response-headers

Conversation

@credence-the-bot
Copy link
Copy Markdown
Contributor

@credence-the-bot credence-the-bot Bot commented May 3, 2026

Summary

Pre-existing bug Devin flagged on PR #29440 (discussion r3178602466): the GET /v1/transfers/:transferId/content endpoint never actually sends its documented Content-Length or X-Transfer-SHA256 response headers. They silently fall through to the default { Content-Type: application/octet-stream }.

Root cause

The HTTP adapter calls r.handler BEFORE resolveResponseHeaders (http-adapter.ts:107-125). handleTransferContentGet consumes the entry via getTransferContent (single-use — deletes it from the transfers map). By the time resolveTransferContentGetHeaders runs, the entry is gone, so the in-resolver getTransferContent call returns null and the resolver falls through to the default-only return.

Fix

HostTransferProxy.getTransferContent now stashes size/sha256 in a justConsumedMetadata Map synchronously, before deleting the entry. A new takeJustConsumedTransferMetadata(transferId) returns + clears that metadata; the header resolver reads from it instead of trying to access the already-consumed entry.

A setTimeout(..., 30_000).unref?.() clears stale entries if the resolver never runs (handler error after consume, request abort), so the cache can't grow unbounded. unref() keeps the timer from holding the process open.

Why a separate PR

Devin flagged this as pre-existing on both PR #29434 and PR #29440 (the Phase 3 final). Out of scope for the Phase 3 feature work — both reviewers agreed it was orthogonal. Filing as a focused follow-up.

Observable impact today

None — the macOS Swift client's pullTransferContent doesn't currently inspect either response header. This restores the documented response contract so future consumers (and any clients verifying transfer integrity over the wire) get correct values.

Tests

4 new cases in host-transfer-proxy.test.ts under a takeJustConsumedTransferMetadata describe block:

  • Returns size+sha256 immediately after getTransferContent
  • Single-use: second take returns null
  • Returns null when getTransferContent was never called
  • Returns null for unknown transferId even after a different transfer was consumed

All 30 proxy tests + 24 routes-targeted tests pass locally. Type check + lint clean.

Files

  • assistant/src/daemon/host-transfer-proxy.ts — adds justConsumedMetadata Map, populates it inside getTransferContent, exposes takeJustConsumedTransferMetadata
  • assistant/src/runtime/routes/host-transfer-routes.tsresolveTransferContentGetHeaders reads from the new take method
  • assistant/src/__tests__/host-transfer-proxy.test.ts — 4 new test cases

Open in Devin Review

…content

The HTTP adapter calls 'r.handler' BEFORE 'resolveResponseHeaders'
(http-adapter.ts:107-125), so when 'handleTransferContentGet' consumes
the entry via 'getTransferContent', the entry is gone by the time
'resolveTransferContentGetHeaders' runs. The resolver silently fell
through to the default '{ Content-Type: application/octet-stream }' —
the documented 'Content-Length' and 'X-Transfer-SHA256' response
headers were never sent.

Fix: HostTransferProxy stashes size/sha256 in a 'justConsumedMetadata'
Map synchronously inside 'getTransferContent' (before the entry is
deleted), and exposes 'takeJustConsumedTransferMetadata' which the
header resolver reads + clears on response. A 30s 'unref()'d timer
clears stale entries if the resolver never runs (e.g., handler error
after consume, abort).

This is pre-existing — Devin flagged it on PR #29440 as r3178602466.
The Swift client's 'pullTransferContent' doesn't currently inspect
either header, so there's no observable functional change today; the
fix restores the documented response contract.

Tests: 4 new cases in 'takeJustConsumedTransferMetadata' covering
post-consume read, single-use semantics, never-consumed transferIds,
and unknown ids in the presence of a different consumed transfer.
@credence-the-bot
Copy link
Copy Markdown
Contributor Author

@codexbot review

@credence-the-bot
Copy link
Copy Markdown
Contributor Author

@devin-ai-integration[bot] please review

@chatgpt-codex-connector
Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Already looking forward to the next diff.

ℹ️ 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".

Copy link
Copy Markdown
Contributor

@devin-ai-integration devin-ai-integration Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Devin Review: No Issues Found

Devin Review analyzed this PR and found no potential bugs to report.

View in Devin Review to see 3 additional findings.

Open in Devin Review

@noanflaherty noanflaherty merged commit e966037 into main May 3, 2026
13 checks passed
@noanflaherty noanflaherty deleted the credence/transfer-content-response-headers branch May 3, 2026 21:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant