Skip to content

[One Workflow] fix: clarify connector response size limit errors - #268591

Merged
shahargl merged 13 commits into
elastic:mainfrom
shahargl:fix/workflow-actions-response-limit-error
May 24, 2026
Merged

[One Workflow] fix: clarify connector response size limit errors#268591
shahargl merged 13 commits into
elastic:mainfrom
shahargl:fix/workflow-actions-response-limit-error

Conversation

@shahargl

@shahargl shahargl commented May 10, 2026

Copy link
Copy Markdown
Contributor

Summary

This PR improves how Workflows reports and handles large responses from spec-generated connectors.

The original issue was discovered while using google_drive.downloadFile in a workflow. A 10-20 MB Google Drive file failed with confusing step-size errors, even when max-step-size looked high enough. The UX made it appear that the workflow memory/output guardrail was the source of the failure, but the actual first limit being hit was often the Actions HTTP client response limit: xpack.actions.maxResponseContentLength.

Before this change, spec connectors could fail inside the Actions Axios client before Workflows had a chance to build or measure the step output. Those failures surfaced as generic connector errors or misleading StepSizeLimitExceeded messages, without making it clear that the response was rejected by Actions before workflow output storage.

Investigation

1. Original Google Drive failure

The first failing case was a workflow that selected a Google Drive file and called:

- name: download_file
  type: google_drive.downloadFile
  connector-id: elastic-drive-shahar
  max-step-size: 1000mb
  with:
    fileId: "{{ steps.get_latest_file.output.files[0].id }}"

The user-facing error implied the workflow step output size limit was the problem. In practice there are two separate limits:

  • xpack.actions.maxResponseContentLength: enforced by the Actions Axios client before connector output exists.
  • max-step-size: enforced by Workflows on serialized step output after the connector returns data.

For spec-generated connectors, the Actions Axios limit was being hit first in many cases. Changing only workflow output limits did not always affect the underlying Axios request limit, which made the error misleading.

2. Root cause: Actions transport limit vs workflow output limit

The key distinction:

  • If Axios rejects with maxContentLength, the connector response never becomes workflow output.
  • If Axios succeeds but the returned object serializes larger than max-step-size, Workflows rejects it with StepSizeLimitExceeded.

Google Drive made this especially confusing because file downloads are returned as base64. A 17 MB file becomes roughly 22.7 MB of serialized workflow output, so the raw response size and stored workflow output size differ.

This PR makes those cases explicit:

  • ActionsResponseContentLengthExceeded: the Actions HTTP client rejected the connector response before workflow output was built.
  • StepSizeLimitExceeded: Workflows built the output, measured it, and rejected the serialized output.

Chosen approach

We discussed a few options and chose the most direct, high-value fix.

Generic header extraction

For normal HTTP responses, the generated spec-connector executor now tries to extract response size from Axios error headers.

Default behavior:

  • Read content-length from error.response.headers.
  • If unavailable, read content-length from error.request.res.headers.
  • Pass the parsed value through errorMeta.contentLengthBytes.

Connector specs can also declare a custom header:

responseSizeHeader?: string;

Example use case: a provider that advertises size with a header like x-resource-size instead of content-length.

Step-level max-step-size as the spec-connector runtime override

For spec connectors, explicit step-level max-step-size is now passed down to the generated connector executor as reserved fetcher.max_content_length, which then overrides Axios maxContentLength.

That means this raises both relevant limits for spec connectors:

- name: download_file
  type: google_drive.downloadFile
  connector-id: elastic-drive-shahar
  max-step-size: 30mb
  with:
    fileId: "..."

This is intentionally step-level. We only pass the override when the step explicitly sets max-step-size, so we do not silently change connector transport behavior from global defaults.

Connector-provided metadata for harder cases

Some providers do not expose useful response headers on the failing download response.

Google Drive is the main example. The actual media response was chunked and did not expose content-length, but Drive file metadata already contains the file size.

For those cases, connector code can attach metadata to the thrown error:

setConnectorActionErrorMeta(error, {
  contentLengthBytes: rawFileSizeBytes,
  estimatedOutputBytes: estimatedBase64OutputBytes,
});

The generated executor merges this metadata with any Axios-derived header metadata.

This keeps the framework generic, while still allowing connectors with provider-specific knowledge to provide better hints.

Cases fixed

Google Drive

google_drive.downloadFile now:

  • Fetches file metadata before download.
  • Uses Drive size as contentLengthBytes when download fails.
  • Estimates stored base64 output size.
  • Attaches both values via setConnectorActionErrorMeta.

Generic spec connectors

The generated executor now:

  • Accepts reserved fetcher.max_content_length.
  • Passes it to getAxiosInstanceWithAuth.
  • Extracts response size from headers on Axios failures.
  • Merges connector-provided metadata when available.

This covers providers that expose content-length without connector-specific work.

Actions Axios client

getAxiosInstanceWithAuth now accepts an optional maxContentLength override and logs debug metadata for maxContentLength failures.

The debug log includes sanitized response headers and header keys. This helped confirm cases like:

  • Jina returning transfer-encoding: chunked and no content-length.
  • S3 returning content-length in request.res.headers.

Sensitive headers are redacted.

Workflow errors

ActionsResponseContentLengthExceeded was added for Actions transport failures.

It distinguishes between:

  • Cases where max-step-size can help because this is a spec connector override path.
  • Cases where only xpack.actions.maxResponseContentLength can help.

When exact size is known, the message suggests an exact max-step-size. When exact size is unknown, it avoids pretending that “above 1 MB” is enough and says to set a larger max-step-size.

StepSizeLimitExceeded was also improved to include actual serialized output size when Workflows has already built the output.

Jina

CleanShot 2026-05-10 at 16 46 31

Manual testing with jina.browse found a separate connector bug.

When Axios threw a transport error without response, Jina’s error handling tried to read:

err.response.data

This masked the real maxContentLength error with:

Cannot read properties of undefined (reading 'data')

This PR fixes Jina to only unwrap structured Jina API errors when response.data.code exists, and otherwise rethrow the original transport error.

Amazon S3

Manual testing with amazon_s3.downloadFile found another important case.

S3 has its own connector-level guardrail: if the file exceeds maximumDownloadSizeBytes, it returns a presigned URL instead of content. When that guard is explicitly raised and the download reaches Axios, S3 responses include content-length.

However, the S3 connector wrapped Axios errors in a new AWS S3 error (...), which discarded the original Axios headers before the generic executor could extract them.

This PR preserves size metadata when S3 wraps Axios download errors, so Workflows can still show contentLengthBytes, estimatedOutputBytes, and suggestedLimitBytes.

Testing

Automated tests cover:

  • Connector action error metadata helpers.
  • Generated executor fetcher.max_content_length behavior.
  • Generic response size header extraction.
  • Connector-provided metadata merge behavior.
  • Generated params schema allowing reserved fetcher options.
  • ActionsResponseContentLengthExceeded messaging.
  • StepSizeLimitExceeded actual/estimated/suggested size messaging.
  • Google Drive file size hint attachment.
  • Jina transport error preservation.
  • S3 metadata preservation when wrapping Axios errors.
  • Actions Axios maxContentLength override and debug logging.

Commands run:

  • node scripts/jest src/platform/packages/shared/kbn-connector-specs/src/connector_spec.test.ts src/platform/packages/shared/kbn-connector-specs/src/specs/google_drive/google_drive.test.ts
  • node scripts/jest src/platform/packages/shared/kbn-connector-specs/src/specs/amazon_s3/amazon_s3_api.test.ts
  • node scripts/jest src/platform/packages/shared/kbn-connector-specs/src/specs/jina/jina_reader.test.ts
  • node scripts/jest src/platform/plugins/shared/workflows_execution_engine/server/step/connector_step.test.ts src/platform/plugins/shared/workflows_execution_engine/server/step/errors.test.ts src/platform/plugins/shared/workflows_execution_engine/server/step/node_implementation.test.ts
  • node scripts/jest x-pack/platform/plugins/shared/actions/server/lib/get_axios_instance.test.ts x-pack/platform/plugins/shared/actions/server/lib/single_file_connectors/generate_executor_function.test.ts x-pack/platform/plugins/shared/actions/server/lib/single_file_connectors/generate_params_schema.test.ts
  • node scripts/check_changes.ts

Manual validation covered:

  • Google Drive file download.
  • Jina browse responses with and without content-length.
  • Amazon S3 download with connector guardrail and with raised maximumDownloadSizeBytes.
  • Existing HTTP connector behavior.

References

Closes elastic/security-team#17243

Made with Cursor

@shahargl shahargl added release_note:fix Team:One Workflow Team label for One Workflow (Workflow automation) v9.3.0 v9.4.0 labels May 10, 2026
@shahargl
shahargl requested review from a team as code owners May 10, 2026 13:35
@shahargl shahargl added v9.5.0 backport:version Backport to applied version labels v9.4.1 and removed v9.3.0 v9.4.0 labels May 10, 2026

@nomagick nomagick left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM on the changes to Jina Reader connector. Thanks for fixing of the error handling bug.

@shahargl shahargl self-assigned this May 11, 2026

@jcger jcger left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

IIUC we already have an approach to define a specific connector/action error. Please have a look at ConnectorAuthorizationError in #260351. We should try to leverage/follow the same pattern for new errors

@@ -18,6 +19,14 @@ const GOOGLE_WORKSPACE_MIME_PREFIX = 'application/vnd.google-apps.';
const DEFAULT_EXPORT_MIME_TYPE = 'application/pdf';
// XLSX preserves tabular structure better than PDF for spreadsheets
const SHEETS_EXPORT_MIME_TYPE = 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet';
const ESTIMATED_JSON_OUTPUT_OVERHEAD_BYTES = 1024;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

where does this number come from exactly? The same value is used in amazon_s3, why 1024 in particular?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

re: ConnectorAuthorizationError

Looked at ConnectorAuthorizationError - the key difference is that with auth errors, our code creates and throws the error from scratch. Here the error is created by Axios internally - we just catch it.

We can't subclass AxiosError because we don't have its constructor args, we only have the finished error object. And wrapping it in a new class would mean copying over all Axios properties (response, headers, status, isAxiosError, etc.) manually, which is fragile.

The WeakMap just attaches metadata to the existing Axios error object without touching it, so nothing downstream needs to change. Happy to discuss if you see a cleaner approach though.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

re: 1024

It's just a number that's comfortably larger than the JSON envelope size (status, headers, key names, etc.). The actual envelope is a few hundred bytes - 1024 is a safe round upper bound. The S3 128 * 1024 is unrelated (MAX_DOWNLOAD_FILE_SIZE_BYTES). Will add a comment to clarify.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

re: ConnectorAuthorizationError

I understand the error is generated by Axios, but we could transform it into a connector framework specific error to keep the same architecture introduced by ConnectorAuthorizationError

What I'd suggest is: add a typed ConnectorResponseSizeLimitError to kbn-connector-specs, catch the Axios size-limit error and throw it as a typed error, have action_executor.ts handle it like ConnectorAuthorizationError (sets errorName), and have connector_step.ts detect errorName === 'ConnectorResponseSizeLimitError' instead of string-matching the message.

This would make the contract between the two layers explicit and typed rather than an untyped bag + string match.

I put together a more complete draft of what I mean here jcger#4

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Thanks for the detailed draft! Agree this is the right direction architecturally - a typed error is cleaner than string-matching an שxios internal message.

I'd prefer to land this as a follow-up though, for a few reasons:

  1. It adds changes to action_executor.ts (core Actions code) which widens the review scope beyond this PR's intent
  2. The axios error detection in the interceptor (ERR_BAD_RESPONSE + config comparison) needs careful validation - the current string match, while ugly, is battle-tested
  3. This PR is already at 20+ files; adding the typed error path would nearly double the change set

Happy to either take this as an immediate follow-up PR, or if you'd prefer to own it based on your draft, that works too. The foundation (shared utils, responseSizeHeader, errorMeta) is all in place for the typed error to build on top of.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Although I disagree with some statements, I'm fine moving forward. I'll finish the review of the current approach and you can follow up with the typed error path.

@shahargl
shahargl requested a review from jcger May 12, 2026 10:32

@Apmats Apmats left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Fundamentally looks fine for me and thanks for addressing this.


const connectorActionErrorMeta = new WeakMap<object, ConnectorActionErrorMeta>();

const getFinitePositiveNumber = (value: unknown): number | undefined => {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Maybe export this to reuse across other connectors?

@shahargl shahargl May 13, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done - getFinitePositiveNumber is now exported from connector_spec.ts and re-exported from the package index. Both google_drive and amazon_s3 import it from the shared location.

return numericValue;
};

export const setConnectorActionErrorMeta = (

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Is this to avoid touching errors themselves? Seems unweildly.

@shahargl shahargl May 13, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Yes - the error is created by Axios internally, so we can't subclass it or add typed properties. The WeakMap lets us attach metadata without mutating the error object. Open to alternatives if you have a cleaner pattern in mind.

@artem-shelkovnikov artem-shelkovnikov left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I've checked only the connectors part, left comments/questions about mechanical stuff in the PR

return numericValue;
};

const getHeaderValue = ({

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

@shahargl shahargl May 13, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done - moved getFinitePositiveNumber, getEstimatedBase64OutputBytes, getHeaderValue, getResponseContentLengthBytes, and ESTIMATED_JSON_OUTPUT_OVERHEAD_BYTES to connector_spec.ts and exported them from the package. Both google_drive and amazon_s3 now import from the shared location.

const getEstimatedBase64OutputBytes = (rawBytes: number): number =>
Math.ceil(rawBytes / 3) * 4 + ESTIMATED_JSON_OUTPUT_OVERHEAD_BYTES;

const getResponseContentLengthBytes = (error: unknown): number | undefined => {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Is there a way to not have it as error: unknown?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Moved to a shared getResponseContentLengthBytes in connector_spec.ts. The error: unknown param type stays since this is called from catch blocks where TS types the caught value as unknown. The as cast is now contained in one place.

};

const getEstimatedBase64OutputBytes = (rawBytes: number): number =>
Math.ceil(rawBytes / 3) * 4 + ESTIMATED_JSON_OUTPUT_OVERHEAD_BYTES;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Where's the math coming from? Also, should it live elsewhere, not in the connector?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Base64 encoding expands 3 raw bytes into 4 characters (Math.ceil(rawBytes / 3) * 4), plus ESTIMATED_JSON_OUTPUT_OVERHEAD_BYTES (1024) as a safe upper bound for the JSON envelope. Added a comment explaining this. Moved to connector_spec.ts so it's not in the connector anymore.

Comment on lines +56 to +59
const axiosError = error as {
response?: { headers?: unknown };
request?: { res?: { headers?: unknown } };
};

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This looks unnecessary

@shahargl shahargl May 13, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Removed - this local getResponseContentLengthBytes is now imported from the shared connector_spec.ts.

Comment on lines +43 to +50
const getFinitePositiveNumber = (value: unknown): number | undefined => {
const numericValue = typeof value === 'string' ? Number(value) : value;
if (typeof numericValue !== 'number' || !Number.isFinite(numericValue) || numericValue < 0) {
return undefined;
}

return numericValue;
};

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Duplicated function

@shahargl shahargl May 13, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed - now imported from connector_spec.ts.

Comment on lines +52 to +54
const getEstimatedBase64OutputBytes = (rawBytes: number): number =>
Math.ceil(rawBytes / 3) * 4 + ESTIMATED_JSON_OUTPUT_OVERHEAD_BYTES;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Ditto

@shahargl shahargl May 13, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed - same, imported from shared.

@@ -29,6 +40,18 @@ function escapeQueryValue(value: string): string {
return value.replace(/\\/g, '\\\\').replace(/'/g, "\\'");
}

const getFinitePositiveNumber = (value: unknown): number | undefined => {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

can we put this and other reused utilities function into some package/common area and use it instead of redeclaring it every time?

@shahargl shahargl May 13, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done - moved getFinitePositiveNumber, getEstimatedBase64OutputBytes, getHeaderValue, getResponseContentLengthBytes, and ESTIMATED_JSON_OUTPUT_OVERHEAD_BYTES to connector_spec.ts and exported from the package. Both connectors now import from the shared location.

Comment thread src/platform/plugins/shared/workflows_execution_engine/server/step/errors.ts Outdated
const ACTIONS_MAX_CONTENT_LENGTH_ERROR_PATTERN = /maxContentLength size of (\d+) exceeded/;

const getActionsMaxContentLengthLimit = (errorMessage: string): number | undefined => {
const match = errorMessage.match(ACTIONS_MAX_CONTENT_LENGTH_ERROR_PATTERN);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

isn't this a bit fragile?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Unfortunately yes — Axios doesn't expose a typed error code for maxContentLength failures, so regex on the internal message is the only detection method. Added a comment explaining this.

axiosInstance,
secrets
);
configuredAxiosInstance.interceptors.response.use(undefined, (error: unknown) => {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

mind that the configuredAxiosInstance may be a different thing than the one on the input. Perhaps it's worth checking that before attaching the interceptors.

@shahargl shahargl May 13, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This is a pre-existing pattern - our PR just attaches an interceptor to whatever configure() returns, same as the rest of the function already does. The instance identity question exists regardless of this change.

@shahargl
shahargl force-pushed the fix/workflow-actions-response-limit-error branch from 29603c6 to 321616d Compare May 13, 2026 08:04
shahargl and others added 7 commits May 13, 2026 11:18
Surface Actions response-size failures distinctly from workflow output-size failures, and let spec connector steps use max-step-size to raise the generated Axios response limit. Preserve provider size hints for Google Drive and S3 so errors can suggest actionable limits.

Closes elastic/security-team#17243

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Move getFinitePositiveNumber, getEstimatedBase64OutputBytes, getHeaderValue,
getResponseContentLengthBytes, and ESTIMATED_JSON_OUTPUT_OVERHEAD_BYTES to
connector_spec.ts. Import from shared location in google_drive and amazon_s3.
Apply candidates.find() suggestion in errors.ts. Add comment explaining Axios
regex pattern in connector_step.ts.

Co-authored-by: Cursor <cursoragent@cursor.com>
- Fix getResponseContentLengthBytes crash on null/primitive input
- Preserve error metadata when throwGoogleDriveError creates new error
- Fix double getFinitePositiveNumber call in setConnectorActionErrorMeta
- Move misplaced size-hint test to downloadFile describe block
- Import ESTIMATED_JSON_OUTPUT_OVERHEAD_BYTES in tests instead of magic 1024
- De-duplicate helpers in generate_executor_function.ts (import from @kbn/connector-specs)
- Add unit tests for shared helpers (getFinitePositiveNumber, getHeaderValue,
  getResponseContentLengthBytes, getEstimatedBase64OutputBytes)
- Add JSDoc to exported helpers
- Add merge-order comment in generate_executor_function.ts

Co-authored-by: Cursor <cursoragent@cursor.com>
When contentLengthBytes is smaller than the limit, the header clearly
doesn't reflect the actual in-memory size (e.g. compressed responses,
chunked transfers). Hide it from the error message to avoid confusion
like "limit is 1 MB but content length was 300 KB".

Co-authored-by: Cursor <cursoragent@cursor.com>
Jina/Cloudflare decompresses responses before sending them, so
content-length reflects the compressed wire size. The actual
decompressed size is in x-decompressed-content-length. Use
responseSizeHeader to read the correct size for browse and search
actions.

Co-authored-by: Cursor <cursoragent@cursor.com>
@kibanamachine

Copy link
Copy Markdown
Contributor

Friendly reminder: Looks like this PR hasn’t been backported yet.
To create automatically backports add a backport:* label or prevent reminders by adding the backport:skip label.
You can also create backports manually by running node scripts/backport --pr 268591 locally
cc: @shahargl

25 similar comments
@kibanamachine

Copy link
Copy Markdown
Contributor

Friendly reminder: Looks like this PR hasn’t been backported yet.
To create automatically backports add a backport:* label or prevent reminders by adding the backport:skip label.
You can also create backports manually by running node scripts/backport --pr 268591 locally
cc: @shahargl

@kibanamachine

Copy link
Copy Markdown
Contributor

Friendly reminder: Looks like this PR hasn’t been backported yet.
To create automatically backports add a backport:* label or prevent reminders by adding the backport:skip label.
You can also create backports manually by running node scripts/backport --pr 268591 locally
cc: @shahargl

@kibanamachine

Copy link
Copy Markdown
Contributor

Friendly reminder: Looks like this PR hasn’t been backported yet.
To create automatically backports add a backport:* label or prevent reminders by adding the backport:skip label.
You can also create backports manually by running node scripts/backport --pr 268591 locally
cc: @shahargl

@kibanamachine

Copy link
Copy Markdown
Contributor

Friendly reminder: Looks like this PR hasn’t been backported yet.
To create automatically backports add a backport:* label or prevent reminders by adding the backport:skip label.
You can also create backports manually by running node scripts/backport --pr 268591 locally
cc: @shahargl

@kibanamachine

Copy link
Copy Markdown
Contributor

Friendly reminder: Looks like this PR hasn’t been backported yet.
To create automatically backports add a backport:* label or prevent reminders by adding the backport:skip label.
You can also create backports manually by running node scripts/backport --pr 268591 locally
cc: @shahargl

@kibanamachine

Copy link
Copy Markdown
Contributor

Friendly reminder: Looks like this PR hasn’t been backported yet.
To create automatically backports add a backport:* label or prevent reminders by adding the backport:skip label.
You can also create backports manually by running node scripts/backport --pr 268591 locally
cc: @shahargl

@kibanamachine

Copy link
Copy Markdown
Contributor

Friendly reminder: Looks like this PR hasn’t been backported yet.
To create automatically backports add a backport:* label or prevent reminders by adding the backport:skip label.
You can also create backports manually by running node scripts/backport --pr 268591 locally
cc: @shahargl

@kibanamachine

Copy link
Copy Markdown
Contributor

Friendly reminder: Looks like this PR hasn’t been backported yet.
To create automatically backports add a backport:* label or prevent reminders by adding the backport:skip label.
You can also create backports manually by running node scripts/backport --pr 268591 locally
cc: @shahargl

@kibanamachine

Copy link
Copy Markdown
Contributor

Friendly reminder: Looks like this PR hasn’t been backported yet.
To create automatically backports add a backport:* label or prevent reminders by adding the backport:skip label.
You can also create backports manually by running node scripts/backport --pr 268591 locally
cc: @shahargl

@kibanamachine

Copy link
Copy Markdown
Contributor

Friendly reminder: Looks like this PR hasn’t been backported yet.
To create automatically backports add a backport:* label or prevent reminders by adding the backport:skip label.
You can also create backports manually by running node scripts/backport --pr 268591 locally
cc: @shahargl

@kibanamachine

Copy link
Copy Markdown
Contributor

Friendly reminder: Looks like this PR hasn’t been backported yet.
To create automatically backports add a backport:* label or prevent reminders by adding the backport:skip label.
You can also create backports manually by running node scripts/backport --pr 268591 locally
cc: @shahargl

@kibanamachine

Copy link
Copy Markdown
Contributor

Friendly reminder: Looks like this PR hasn’t been backported yet.
To create automatically backports add a backport:* label or prevent reminders by adding the backport:skip label.
You can also create backports manually by running node scripts/backport --pr 268591 locally
cc: @shahargl

@kibanamachine

Copy link
Copy Markdown
Contributor

Friendly reminder: Looks like this PR hasn’t been backported yet.
To create automatically backports add a backport:* label or prevent reminders by adding the backport:skip label.
You can also create backports manually by running node scripts/backport --pr 268591 locally
cc: @shahargl

@kibanamachine

Copy link
Copy Markdown
Contributor

Friendly reminder: Looks like this PR hasn’t been backported yet.
To create automatically backports add a backport:* label or prevent reminders by adding the backport:skip label.
You can also create backports manually by running node scripts/backport --pr 268591 locally
cc: @shahargl

@kibanamachine

Copy link
Copy Markdown
Contributor

Friendly reminder: Looks like this PR hasn’t been backported yet.
To create automatically backports add a backport:* label or prevent reminders by adding the backport:skip label.
You can also create backports manually by running node scripts/backport --pr 268591 locally
cc: @shahargl

@kibanamachine

Copy link
Copy Markdown
Contributor

Friendly reminder: Looks like this PR hasn’t been backported yet.
To create automatically backports add a backport:* label or prevent reminders by adding the backport:skip label.
You can also create backports manually by running node scripts/backport --pr 268591 locally
cc: @shahargl

@kibanamachine

Copy link
Copy Markdown
Contributor

Friendly reminder: Looks like this PR hasn’t been backported yet.
To create automatically backports add a backport:* label or prevent reminders by adding the backport:skip label.
You can also create backports manually by running node scripts/backport --pr 268591 locally
cc: @shahargl

@kibanamachine

Copy link
Copy Markdown
Contributor

Friendly reminder: Looks like this PR hasn’t been backported yet.
To create automatically backports add a backport:* label or prevent reminders by adding the backport:skip label.
You can also create backports manually by running node scripts/backport --pr 268591 locally
cc: @shahargl

@kibanamachine

Copy link
Copy Markdown
Contributor

Friendly reminder: Looks like this PR hasn’t been backported yet.
To create automatically backports add a backport:* label or prevent reminders by adding the backport:skip label.
You can also create backports manually by running node scripts/backport --pr 268591 locally
cc: @shahargl

@kibanamachine

Copy link
Copy Markdown
Contributor

Friendly reminder: Looks like this PR hasn’t been backported yet.
To create automatically backports add a backport:* label or prevent reminders by adding the backport:skip label.
You can also create backports manually by running node scripts/backport --pr 268591 locally
cc: @shahargl

@kibanamachine

Copy link
Copy Markdown
Contributor

Friendly reminder: Looks like this PR hasn’t been backported yet.
To create automatically backports add a backport:* label or prevent reminders by adding the backport:skip label.
You can also create backports manually by running node scripts/backport --pr 268591 locally
cc: @shahargl

@kibanamachine

Copy link
Copy Markdown
Contributor

Friendly reminder: Looks like this PR hasn’t been backported yet.
To create automatically backports add a backport:* label or prevent reminders by adding the backport:skip label.
You can also create backports manually by running node scripts/backport --pr 268591 locally
cc: @shahargl

@kibanamachine

Copy link
Copy Markdown
Contributor

Friendly reminder: Looks like this PR hasn’t been backported yet.
To create automatically backports add a backport:* label or prevent reminders by adding the backport:skip label.
You can also create backports manually by running node scripts/backport --pr 268591 locally
cc: @shahargl

@kibanamachine

Copy link
Copy Markdown
Contributor

Friendly reminder: Looks like this PR hasn’t been backported yet.
To create automatically backports add a backport:* label or prevent reminders by adding the backport:skip label.
You can also create backports manually by running node scripts/backport --pr 268591 locally
cc: @shahargl

@kibanamachine

Copy link
Copy Markdown
Contributor

Friendly reminder: Looks like this PR hasn’t been backported yet.
To create automatically backports add a backport:* label or prevent reminders by adding the backport:skip label.
You can also create backports manually by running node scripts/backport --pr 268591 locally
cc: @shahargl

patrykkopycinski pushed a commit to patrykkopycinski/kibana that referenced this pull request Aug 5, 2026
…stic#268591)

## Summary

This PR improves how Workflows reports and handles large responses from
spec-generated connectors.

The original issue was discovered while using
`google_drive.downloadFile` in a workflow. A 10-20 MB Google Drive file
failed with confusing step-size errors, even when `max-step-size` looked
high enough. The UX made it appear that the workflow memory/output
guardrail was the source of the failure, but the actual first limit
being hit was often the Actions HTTP client response limit:
`xpack.actions.maxResponseContentLength`.

Before this change, spec connectors could fail inside the Actions Axios
client before Workflows had a chance to build or measure the step
output. Those failures surfaced as generic connector errors or
misleading `StepSizeLimitExceeded` messages, without making it clear
that the response was rejected by Actions before workflow output
storage.

## Investigation

### 1. Original Google Drive failure

The first failing case was a workflow that selected a Google Drive file
and called:

```yaml
- name: download_file
  type: google_drive.downloadFile
  connector-id: elastic-drive-shahar
  max-step-size: 1000mb
  with:
    fileId: "{{ steps.get_latest_file.output.files[0].id }}"
```

The user-facing error implied the workflow step output size limit was
the problem. In practice there are two separate limits:

- `xpack.actions.maxResponseContentLength`: enforced by the Actions
Axios client before connector output exists.
- `max-step-size`: enforced by Workflows on serialized step output after
the connector returns data.

For spec-generated connectors, the Actions Axios limit was being hit
first in many cases. Changing only workflow output limits did not always
affect the underlying Axios request limit, which made the error
misleading.

### 2. Root cause: Actions transport limit vs workflow output limit

The key distinction:

- If Axios rejects with `maxContentLength`, the connector response never
becomes workflow output.
- If Axios succeeds but the returned object serializes larger than
`max-step-size`, Workflows rejects it with `StepSizeLimitExceeded`.

Google Drive made this especially confusing because file downloads are
returned as base64. A 17 MB file becomes roughly 22.7 MB of serialized
workflow output, so the raw response size and stored workflow output
size differ.

This PR makes those cases explicit:

- `ActionsResponseContentLengthExceeded`: the Actions HTTP client
rejected the connector response before workflow output was built.
- `StepSizeLimitExceeded`: Workflows built the output, measured it, and
rejected the serialized output.

## Chosen approach

We discussed a few options and chose the most direct, high-value fix.

### Generic header extraction

For normal HTTP responses, the generated spec-connector executor now
tries to extract response size from Axios error headers.

Default behavior:

- Read `content-length` from `error.response.headers`.
- If unavailable, read `content-length` from
`error.request.res.headers`.
- Pass the parsed value through `errorMeta.contentLengthBytes`.

Connector specs can also declare a custom header:

```ts
responseSizeHeader?: string;
```

Example use case: a provider that advertises size with a header like
`x-resource-size` instead of `content-length`.

### Step-level `max-step-size` as the spec-connector runtime override

For spec connectors, explicit step-level `max-step-size` is now passed
down to the generated connector executor as reserved
`fetcher.max_content_length`, which then overrides Axios
`maxContentLength`.

That means this raises both relevant limits for spec connectors:

```yaml
- name: download_file
  type: google_drive.downloadFile
  connector-id: elastic-drive-shahar
  max-step-size: 30mb
  with:
    fileId: "..."
```

This is intentionally step-level. We only pass the override when the
step explicitly sets `max-step-size`, so we do not silently change
connector transport behavior from global defaults.

### Connector-provided metadata for harder cases

Some providers do not expose useful response headers on the failing
download response.

Google Drive is the main example. The actual media response was chunked
and did not expose `content-length`, but Drive file metadata already
contains the file size.

For those cases, connector code can attach metadata to the thrown error:

```ts
setConnectorActionErrorMeta(error, {
  contentLengthBytes: rawFileSizeBytes,
  estimatedOutputBytes: estimatedBase64OutputBytes,
});
```

The generated executor merges this metadata with any Axios-derived
header metadata.

This keeps the framework generic, while still allowing connectors with
provider-specific knowledge to provide better hints.

## Cases fixed

### Google Drive

`google_drive.downloadFile` now:

- Fetches file metadata before download.
- Uses Drive `size` as `contentLengthBytes` when download fails.
- Estimates stored base64 output size.
- Attaches both values via `setConnectorActionErrorMeta`.

### Generic spec connectors

The generated executor now:

- Accepts reserved `fetcher.max_content_length`.
- Passes it to `getAxiosInstanceWithAuth`.
- Extracts response size from headers on Axios failures.
- Merges connector-provided metadata when available.

This covers providers that expose `content-length` without
connector-specific work.

### Actions Axios client

`getAxiosInstanceWithAuth` now accepts an optional `maxContentLength`
override and logs debug metadata for `maxContentLength` failures.

The debug log includes sanitized response headers and header keys. This
helped confirm cases like:

- Jina returning `transfer-encoding: chunked` and no `content-length`.
- S3 returning `content-length` in `request.res.headers`.

Sensitive headers are redacted.

### Workflow errors

`ActionsResponseContentLengthExceeded` was added for Actions transport
failures.

It distinguishes between:

- Cases where `max-step-size` can help because this is a spec connector
override path.
- Cases where only `xpack.actions.maxResponseContentLength` can help.

When exact size is known, the message suggests an exact `max-step-size`.
When exact size is unknown, it avoids pretending that “above 1 MB” is
enough and says to set a larger `max-step-size`.

`StepSizeLimitExceeded` was also improved to include actual serialized
output size when Workflows has already built the output.

### Jina

<img width="2341" height="494" alt="CleanShot 2026-05-10 at 16 46 31"
src="https://github.com/user-attachments/assets/57dafa4d-5deb-4ecf-8926-07b99479a2d3"
/>


Manual testing with `jina.browse` found a separate connector bug.

When Axios threw a transport error without `response`, Jina’s error
handling tried to read:

```ts
err.response.data
```

This masked the real `maxContentLength` error with:

```text
Cannot read properties of undefined (reading 'data')
```

This PR fixes Jina to only unwrap structured Jina API errors when
`response.data.code` exists, and otherwise rethrow the original
transport error.

### Amazon S3

Manual testing with `amazon_s3.downloadFile` found another important
case.

S3 has its own connector-level guardrail: if the file exceeds
`maximumDownloadSizeBytes`, it returns a presigned URL instead of
content. When that guard is explicitly raised and the download reaches
Axios, S3 responses include `content-length`.

However, the S3 connector wrapped Axios errors in a new `AWS S3 error
(...)`, which discarded the original Axios headers before the generic
executor could extract them.

This PR preserves size metadata when S3 wraps Axios download errors, so
Workflows can still show `contentLengthBytes`, `estimatedOutputBytes`,
and `suggestedLimitBytes`.

## Testing

Automated tests cover:

- Connector action error metadata helpers.
- Generated executor `fetcher.max_content_length` behavior.
- Generic response size header extraction.
- Connector-provided metadata merge behavior.
- Generated params schema allowing reserved `fetcher` options.
- `ActionsResponseContentLengthExceeded` messaging.
- `StepSizeLimitExceeded` actual/estimated/suggested size messaging.
- Google Drive file size hint attachment.
- Jina transport error preservation.
- S3 metadata preservation when wrapping Axios errors.
- Actions Axios `maxContentLength` override and debug logging.

Commands run:

- `node scripts/jest
src/platform/packages/shared/kbn-connector-specs/src/connector_spec.test.ts
src/platform/packages/shared/kbn-connector-specs/src/specs/google_drive/google_drive.test.ts`
- `node scripts/jest
src/platform/packages/shared/kbn-connector-specs/src/specs/amazon_s3/amazon_s3_api.test.ts`
- `node scripts/jest
src/platform/packages/shared/kbn-connector-specs/src/specs/jina/jina_reader.test.ts`
- `node scripts/jest
src/platform/plugins/shared/workflows_execution_engine/server/step/connector_step.test.ts
src/platform/plugins/shared/workflows_execution_engine/server/step/errors.test.ts
src/platform/plugins/shared/workflows_execution_engine/server/step/node_implementation.test.ts`
- `node scripts/jest
x-pack/platform/plugins/shared/actions/server/lib/get_axios_instance.test.ts
x-pack/platform/plugins/shared/actions/server/lib/single_file_connectors/generate_executor_function.test.ts
x-pack/platform/plugins/shared/actions/server/lib/single_file_connectors/generate_params_schema.test.ts`
- `node scripts/check_changes.ts`

Manual validation covered:

- Google Drive file download.
- Jina browse responses with and without `content-length`.
- Amazon S3 download with connector guardrail and with raised
`maximumDownloadSizeBytes`.
- Existing HTTP connector behavior.

## References

Closes elastic/security-team#17243

Made with [Cursor](https://cursor.com)

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
patrykkopycinski pushed a commit to patrykkopycinski/kibana that referenced this pull request Aug 5, 2026
…astic#270978)

## Summary

Fixes a regression introduced by elastic#268591 where all HTTP workflow steps
fail with `Unrecognized key: "fetchOptions"`.

- PR elastic#268591 renamed `fetcher` to `fetchOptions` for spec-based
connectors, but mistakenly applied the rename to the HTTP system
connector injection path in `connector_step.ts`
- The HTTP system connector schema expects `fetcher`, not `fetchOptions`
- Fix: use the correct field name based on connector type — `fetcher`
for `CONNECTOR_TYPES_WITH_LAYER_1` (HTTP system connector) and
`fetchOptions` for spec-based connectors

## Test Plan

- [x] Unit tests updated and passing (14/14)
- [ ] Manual: run a workflow with `type: http` step and verify it
succeeds
- [ ] Manual: run a workflow with a spec connector step (e.g.
`google_drive.downloadFile`) with `max-step-size` and verify
`fetchOptions` is injected correctly

## References

Closes elastic/security-team#17553

Made with [Cursor](https://cursor.com)

Co-authored-by: Cursor <cursoragent@cursor.com>
@kibanamachine

Copy link
Copy Markdown
Contributor

Friendly reminder: Looks like this PR hasn’t been backported yet.
To create automatically backports add a backport:* label or prevent reminders by adding the backport:skip label.
You can also create backports manually by running node scripts/backport --pr 268591 locally
cc: @shahargl

1 similar comment
@kibanamachine

Copy link
Copy Markdown
Contributor

Friendly reminder: Looks like this PR hasn’t been backported yet.
To create automatically backports add a backport:* label or prevent reminders by adding the backport:skip label.
You can also create backports manually by running node scripts/backport --pr 268591 locally
cc: @shahargl

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

backport missing Added to PRs automatically when the are determined to be missing a backport. backport:version Backport to applied version labels release_note:fix Team:One Workflow Team label for One Workflow (Workflow automation) v9.4.1 v9.5.0

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants