Skip to content

feat: attachment skip list for failing uploads#228

Open
gasperzgonec wants to merge 7 commits into
mainfrom
gasperz/attachment-skip-list
Open

feat: attachment skip list for failing uploads#228
gasperzgonec wants to merge 7 commits into
mainfrom
gasperz/attachment-skip-list

Conversation

@gasperzgonec

Copy link
Copy Markdown
Contributor

Description

Connected Issues

Checklist

  • Tests added/updated and ran with npm run test OR no tests needed.
  • Ran backwards compatibility tests with npm run test:backwards-compatibility.
  • Code formatted and checked with npm run lint.
  • Tested airdrop-template linked to this PR.
  • Documentation updated and provided a link to PR / new docs OR no docs needed.

gasperzgonec and others added 2 commits July 23, 2026 10:49
Attachments that deterministically fail with ECONNABORTED or a 5xx from
the SDK-owned upload path were being retried from scratch on every lambda
invocation forever, since lastProcessedAttachmentsIdsList only tracks
successes. Track per-attachment transient failure counts across
invocations and skip an attachment once it exceeds the configurable
maxAttachmentFailures (default 3) WorkerAdapterOption.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…t classification

Connector-owned stream() callbacks (e.g. fetching from the external
system) had no way to report a status code, so their errors could never
be classified as transient like the SDK-owned upload path already is.
Add an optional statusCode field to ExternalSystemAttachmentStreamingResponse.error
and treat 5xx as transient in processAttachment, so connector-reported
failures count toward the same maxAttachmentFailures skip-list.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@gasperzgonec
gasperzgonec requested review from a team and radovanjorgic as code owners July 23, 2026 09:10
gasperzgonec and others added 5 commits July 23, 2026 11:21
…heck

The backwards-compatibility test compared interface/class property types
by exact string equality, so widening `ErrorRecord` to
`ErrorRecord & { statusCode?: number }` (an additive, non-breaking change
since the new field is optional) was flagged as a breaking change.

Hoist the existing object-type compatibility checker to module scope and
extend it to also handle intersection types: a new intersection member is
accepted as long as it only adds optional properties. Apply the same
check to interface and class property types instead of exact string
equality.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…ot across

Previously, transient upload failures (ECONNABORTED, 5xx) were counted
across invocations before an attachment was marked permanently failed:
each invocation retried once, incremented a persisted counter, and gave
up only after maxAttachmentFailures separate invocations. That meant an
attachment could go 2 invocations without a real retry attempt beyond
the one HTTP-layer retry axios-retry already does.

Move the retry loop inside a single invocation instead: an attachment is
retried immediately up to maxAttachmentFailures times in the same
processAttachmentWithRetries call, and only added to
failedAttachmentsIdsList (skipped in subsequent invocations) once it
exhausts that budget within one invocation. If a timeout interrupts a
retry sequence, the attachment is left unmarked so it's retried fresh
next invocation rather than losing its progress. This still prevents the
infinite-loop failure mode (deterministic failures retried forever
across invocations) while giving genuinely intermittent failures several
real attempts before giving up, without persisting a failure count.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The stream function's HTTP client (axiosClient) already retries transient
errors (ECONNABORTED, 5xx) with exponential backoff before returning one
to processAttachment. Adding a second retry loop on top of that (from the
previous commit) meant re-attempting a request whose retry budget was
already exhausted at the HTTP layer, effectively multiplying an
already-failed wait instead of giving up sooner.

Mark an attachment permanently failed on the first transient error
observed here, removing the in-invocation retry loop, maxAttachmentFailures
option, and its default constant along with it.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Add the attachment's fetch URL and a safe allowlist of response headers
(content-length, content-type, content-disposition, content-encoding,
last-modified, etag, cache-control) to every "Skipping attachment" log
line, to make it easier to diagnose why a specific attachment failed
(e.g. correlating with the external system's rate limits or file size).

Response headers are only ever passed through an explicit allowlist
(getSafeResponseHeaders) rather than logged wholesale, since attachment
fetch responses can carry cookies or other headers that shouldn't end up
in logs — this mirrors the same allowlist-over-denylist approach already
used for sensitive header stripping elsewhere in the SDK.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
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