Repository navigation
fix(cli): deploy hangs forever when an AWS call gets no response - #2105
Open
abhiramshaji-cyber wants to merge 2 commits into
Open
abhiramshaji-cyber wants to merge 2 commits into
abhiramshaji-cyber wants to merge 2 commits into
Conversation
Every SDK client gets a requestTimeout of 5 minutes, but without throwOnRequestTimeout Smithy only logs a warning when it passes. The request is never failed, so the retry strategy never sees it, and a connection that dies silently keeps the CLI waiting until something outside kills it. Add a 60 second socketTimeout instead. It fires only when no data has moved in either direction for that long, so slow but healthy calls such as large asset uploads keep working, which a total request timeout would cut off. The resulting TimeoutError is classified as transient, so the existing retry strategy retries it on a new connection and fails the command once retries run out. 60 seconds matches the AWS CLI read timeout default. Both the CLI and the toolkit library build their request handler through sdkRequestHandler, with or without a proxy agent, so this covers both. The field is added to RequestHandlerSettings so custom base credential providers receive it too. Closes aws#2097
A 60 second idle timeout on every client would cut off a hotswap of a BucketDeployment. Hotswap invokes its custom resource function synchronously, and Lambda sends nothing on the connection until the function returns, which can take up to 15 minutes. The retry strategy would then start up to 7 overlapping runs of the same sync before failing a command that works today. The shared idle timeout is now 300 seconds, the same value requestTimeout already had. Older Smithy versions applied requestTimeout as an idle timeout, so this restores the behavior the CLI ran with for years while still turning an endless hang into a bounded failure. The Lambda client gets 905 seconds, just above the Lambda maximum, so a long running invoke is never dropped and retried while it is still working.
abhiramshaji-cyber
had a problem deploying
to
automation
October 29, 2026 06:47 — with
GitHub Actions
Failure
abhiramshaji-cyber
requested a deployment
to
integ-approval
October 29, 2026 06:47 — with
GitHub Actions
Waiting
aws-cdk-automation
enabled auto-merge
October 29, 2026 06:47
This branch is waiting to be deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
A deploy that got no answer from AWS used to wait until the job was killed, and now each silent call is dropped after 5 minutes and retried.
What this code is for
sdkRequestHandlerinpackages/@aws-cdk/toolkit-lib/lib/api/aws-auth/awscli-compatible.tsbuilds the HTTP settings that every AWS SDK client in the CLI and in the toolkit library uses. Bothpackages/aws-cdk/lib/cli/cli.ts(with the proxy agent, if any) andToolkit.sdkProviderintoolkit.ts(withsdkConfig.httpOptions.agent, if any) call it, and the result is passed to every client next to aConfiguredRetryStrategythat retries timeouts and other network errors. The one exception after this PR isSDK.lambda()insdk.ts, which overrides the new idle timeout for the Lambda client.Before this PR
A GitHub Actions job runs
cdk deploy, and the connection under oneDescribeStackscall goes silent after the request was sent, as described in #2097.DescribeStacksthroughNodeHttpHandlerwithconnectionTimeout: 10000andrequestTimeout: 300000.connectionTimeoutnever applies.requestTimeoutpasses.throwOnRequestTimeoutis not set, so Smithy only logs[WARN] a request has exceeded the configured 300000 ms requestTimeout.ConfiguredRetryStrategynever sees an error and never retries.main: one connection, the warning at 300 s, and still waiting at 330 s.After this PR
Same job, same silent
DescribeStackscall.socketTimeout: 300000.TimeoutError.ConfiguredRetryStrategytreatsTimeoutErroras a temporary error and sends the call again on a new connection.TimeoutErrorinstead of waiting. There are 7 attempts in total, so that is about 35 minutes plus the short retry delays. Against the same silent local server with a 60 s value, I saw the call retried on connections 2 to 7 and then failed at about 467 s, which is the same path with a shorter timer.BucketDeploymentinvokes its Lambda function synchronously, and Lambda sends nothing back until the function returns, which can take up to 15 minutes. The Lambda client therefore getssocketTimeout: 905000, just above that maximum, so a longs3 syncis never dropped and started again while it is still running.The diagram
flowchart LR classDef box fill:#eef1ff,stroke:#4c5bd4,color:#111827 classDef bad fill:#ffd6d6,stroke:#cc0000,color:#111827,stroke-width:3px classDef good fill:#c8f0c8,stroke:#008800,color:#111827,stroke-width:3px subgraph BEFORE["BEFORE: waits until the job is killed, 1 connection"] direction TB B1["cdk deploy calls DescribeStacks"] --> B2["NodeHttpHandler sends the request"] B2 --> B3["connection goes silent"] B3 --> B4{"requestTimeout 300000 passes"} B4 -->|"throwOnRequestTimeout not set"| B5["logs WARN and keeps waiting forever"] B4 -->|"response arrives"| B6["deploy continues"] end subgraph AFTER["AFTER: retried every 300 s, then fails with TimeoutError"] direction TB A1["cdk deploy calls DescribeStacks"] --> A2["NodeHttpHandler sends the request"] A2 --> A3["connection goes silent"] A3 --> A4{"THE CHANGE: socketTimeout 300000"} A4 -->|"no data for 300 s"| A5["TimeoutError, retried on a new connection"] A4 -->|"data still moving"| A6["slow upload keeps going"] end B1 ~~~ A1 class B1,B2,B3,B6 box class A1,A2,A3,A6 box class B4,B5 bad class A4,A5 good linkStyle 3 stroke:#cc0000,stroke-width:5px linkStyle 8 stroke:#008800,stroke-width:5px style BEFORE fill:#fff5f5,stroke:#cc0000,color:#111827 style AFTER fill:#f3fff3,stroke:#008800,color:#111827The change
sdkRequestHandlernow returnssocketTimeout: 300000next to the existingconnectionTimeoutandrequestTimeout. This is the fix. I reused the 300 seconds thatrequestTimeoutalready has, because the Smithy docs forthrowOnRequestTimeoutsayrequestTimeout"was for a long time incorrectly being set as a socket idle timeout". So the CLI already ran with a 5 minute idle timeout that threw for years, and this brings that behavior back on purpose.SDK.lambda()insdk.tsbuilds its client withsocketTimeout: 905000and keeps every other setting. The hotswap of aBucketDeploymentcallsInvokesynchronously and the function may run for the full 15 minutes, so a shorter idle timeout would drop it and the retries would start overlappings3 sync --deleteruns.RequestHandlerSettingsinbase-credentials.tsgets an optionalsocketTimeoutfield, so the value type checks and a customIBaseCredentialsProviderreceives it inclientConfig.requestHandlertoo.requestTimeoutand its warning are left as they are. I did not turn onthrowOnRequestTimeout, because that timer runs from the start of the request until the response headers arrive, so it would fail a healthy upload that takes longer than 5 minutes. @JimNero009 pointed this out in the issue, and their suggestion of asocketTimeoutis what this PR follows. Thank you for the very clear report and reproduction.awscli-compatible.test.ts: one checks the settingssdkRequestHandlerreturns, including the agent on bothhttpAgentandhttpsAgent, and fails without this change. Two more run a realSTSClientagainst a local TCP server with a 100 mssocketTimeout. To be honest about what these two prove: they pass onmaintoo, because they check how Smithy handlessocketTimeoutrather than this change. I kept them as a guard in case a future Smithy version changes that behavior, but I am very happy to drop them if you would rather not have a TCP server in a unit suite: a server that never answers gets a second connection and the call rejects withTimeoutError; a server that sends its whole response, headers included, in small pieces every 50 ms still succeeds on one connection. That second test fails if the settings userequestTimeoutwiththrowOnRequestTimeoutinstead, which is why I went withsocketTimeout.sdk.test.ts: one checks that the Lambda client getssocketTimeout: 905000with the other settings unchanged, and fails without this change; one checks that the CloudFormation client still gets the shared settings.I would really value your view on a few choices, and I am happy to change any of them:
cdk-assetsCLI builds its clients incdk-assets-lib/lib/aws.tswithout any of these timeouts. Would you like the same change there as a separate PR?What changes for a user
TimeoutErrorafter the existing retries, about 35 minutes, instead of running until the CI job is killed.cdk deploy --hotswapandcdk watchwith aBucketDeploymentthat takes many minutes keep working, because the Lambda client waits up to just over 15 minutes.sdkRequestHandler.requestTimeoutwarning after 5 minutes still appears for very long uploads, as it does today.Fixes #2097
Checklist
By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license