Repository: aws/aws-sam-cli

Issue:
How do I increase the time limit for sam deploy?
I believe that the time limit for `sam deploy` is 60 minutes.

I don't see it documented, but I see it in the code of the `wait_for_execute` function.

https://github.com/aws/aws-sam-cli/blob/fc9653313ac0d35e995b80ed8215f5587e20d16a/samcli/lib/deploy/deployer.py#L459-L510

The `SAM_CLI_POLL_DELAY` is documented, but it doesn't seem to be referenced by this function.

I have a stack that often takes more than 1 hour to complete an update (it contains multiple stack sets across many accounts and regions). How can I have sam wait for it to complete?

Discussion:
cullenwatson: > Would you be able to make a unit test where the max_wait_duration is really low like 5 seconds and test that it goes through?

It is in minutes. Is there a way we could test one minute timeout?
jysheng123: Hmm, yeah on second thought, this seems fine to me to test since the previous hard coded value was 120 seconds anyways,  maybe a NIT that im thinking of is the possibility of the sam deploy time outing for an in definite amount of time, so maybe something like setting a reasonable upper limit like 10 minutes to me. 
cullenwatson: ok i just put min of 60 minutes and max 480 minutes as 480 was what the op wanted
hawflau: Hi @cullenwatson could you please check the failed checks and fix accordingly? Seems like there's some linting issues, and you also need to run `make schema` to update the SAM CLI command schema as you added a new command option
lucashuy: Hi @cullenwatson, just wanted to check in to see if you had any blockers or help wanted to continue contributing.
cullenwatson: ok the tests are passing now besides the 2 that are failing also in main

Patch:
diff --git a/requirements/base.txt b/requirements/base.txt
index 52ec5e21..7585657a 100644
--- a/requirements/base.txt
+++ b/requirements/base.txt
@@ -31,4 +31,4 @@ tzlocal==5.2
 cfn-lint~=0.87.7
 
 # Type checking boto3 objects
-boto3-stubs[apigateway,cloudformation,ecr,iam,lambda,s3,schemas,secretsmanager,signer,stepfunctions,sts,xray,sqs,kinesis]==1.34.143
+boto3-stubs[apigateway,cloudformation,ecr,iam,lambda,s3,schemas,secretsmanager,signer,stepfunctions,sts,xray,sqs,kinesis]==1.34.145
diff --git a/requirements/pre-dev.txt b/requirements/pre-dev.txt
index 6d4c5a81..30a0156e 100644
--- a/requirements/pre-dev.txt
+++ b/requirements/pre-dev.txt
@@ -1,1 +1,1 @@
-ruff==0.5.2
+ruff==0.5.3
diff --git a/requirements/reproducible-linux.txt b/requirements/reproducible-linux.txt
index 61cc65d3..d18578cf 100644
--- a/requirements/reproducible-linux.txt
+++ b/requirements/reproducible-linux.txt
@@ -38,25 +38,25 @@ blinker==1.8.2 \
     --hash=sha256:1779309f71bf239144b9399d06ae925637cf6634cf6bd131104184531bf67c01 \
     --hash=sha256:8f77b09d3bf7c795e969e9486f39c2c5e9c39d4ee07424be2bc594ece9642d83
     # via flask
-boto3==1.34.143 \
-    --hash=sha256:0d16832f23e6bd3ae94e35ea8e625529850bfad9baccd426de96ad8f445d8e03 \
-    --hash=sha256:b590ce80c65149194def43ebf0ea1cf0533945502507837389a8d22e3ecbcf05
+boto3==1.34.145 \
+    --hash=sha256:69d5afb7a017d07dd6bdfb680d2912d5d369b3fafa0a45161207d9f393b14d7e \
+    --hash=sha256:ac770fb53dde1743aec56bd8e56b7ee2e2f5ad42a37825968ec4ff8428822640
     # via
     #   aws-sam-cli (setup.py)
     #   aws-sam-translator
-boto3-stubs[apigateway,cloudformation,ecr,iam,kinesis,lambda,s3,schemas,secretsmanager,signer,sqs,stepfunctions,sts,xray]==1.34.143 \
-    --hash=sha256:55f1b549507caac68d1affee2f3d8769a1c993960b562fd014f5840dfce9cd29 \
-    --hash=sha256:7a84989088c160d2f0bd177f6f02632b1e9efac1eb548b8b5cb0cbe3aa8f81a6
+boto3-stubs[apigateway,cloudformation,ecr,iam,kinesis,lambda,s3,schemas,secretsmanager,signer,sqs,stepfunctions,sts,xray]==1.34.145 \
+    --hash=sha256:1303deb7d749a2c9bd5a30830e1d537c1474b70c5f429e3db9af5d3eaa7b9696 \
+    --hash=sha256:2d15ccfc44407baae897d3a531f9eb0826e207d70e92f5268e8703139bec2dba
     # via aws-sam-cli (setup.py)
-botocore==1.34.143 \
-    --hash=sha256:059f032ec05733a836e04e869c5a15534420102f93116f3bc9a5b759b0651caf \
-    --hash=sha256:094aea179e8aaa1bc957ad49cc27d93b189dd3a1f3075d8b0ca7c445a2a88430
+botocore==1.34.145 \
+    --hash=sha256:2e72e262de02adcb0264ac2bac159a28f55dbba8d9e52aa0308773a42950dff5 \
+    --hash=sha256:edf0fb4c02186ae29b76263ac5fda18b0a085d334a310551c9984407cf1079e6
     # via
     #   boto3
     #   s3transfer
-botocore-stubs==1.34.143 \
-    --hash=sha256:2bfd2707c9c6857d21616c6c56ed6efcd335782d36d95217483d902f446420f2 \
-    --hash=sha256:ff6e7ca53dbd73cba8009fd29a99bf27d714936ea24eaffd500ecdcef36c9d5a
+botocore-stubs==1.34.145 \
+    --hash=sha256:4c579734492713c142773c7405cf62b466dfaf5f05371b7510d3cd308b0ba6a5 \
+    --hash=sha256:69bcfc0c81eee294e681dc8049b7ca2a47a8f0510b88c05710f07dba6d0785c7
     # via boto3-stubs
 certifi==2024.7.4 \
     --hash=sha256:5a1e7645bc0ec61a09e26c36f6106dd4cf40c6db3a1fb6352b0244e7fb057c7b \
@@ -437,9 +437,9 @@ mypy-boto3-schemas==1.34.0 \
     --hash=sha256:28c016609dcffc606bd8425ee92894c46e943ab77033e1ae46481f00c39b7f75 \
     --hash=sha256:3b25a71944192b0980c3bb5132deb7c06ee9b88580ed63f257fad97cf3bf2927
     # via boto3-stubs
-mypy-boto3-secretsmanager==1.34.128 \
-    --hash=sha256:7ce9815d116fa1749971691355b1e1c8f462d46e7eaa9d84133b8db96dd3515f \
-    --hash=sha256:ae2b398efa1a32214c3eddb6901efa67cfc24a893b113f549a06bb70bb43b402
+mypy-boto3-secretsmanager==1.34.145 \
+    --hash=sha256:986511caa6626edfed7eb11b63c929801e9468c58e15927dc6fc0339c4eb34cb \
+    --hash=sha256:e5a82c05cce68168a3709e5f0d35066cf250961db1d8670f0111da66206814c7
     # via boto3-stubs
 mypy-boto3-signer==1.34.95 \
     --hash=sha256:0118854f1664f7e27e03f5ce2fea3344cc4985fa6ed15a0d5c7a7146b4a4326f \
@@ -926,9 +926,9 @@ tomlkit==0.13.0 \
     --hash=sha256:08ad192699734149f5b97b45f1f18dad7eb1b6d16bc72ad0c2335772650d7b72 \
     --hash=sha256:7075d3042d03b80f603482d69bf0c8f345c2b30e41699fd8883227f89972b264
     # via aws-sam-cli (setup.py)
-types-awscrt==0.21.0 \
-    --hash=sha256:026f882d4d23f04c5b2ab08d6fefd627842537009cd00e9f78dd4960314d51aa \
-    --hash=sha256:06aa247fe5ccf0b86428e5289aeabf67f967e10861f211c16c19e7d2542a70a9
+types-awscrt==0.21.2 \
+    --hash=sha256:0839fe12f0f914d8f7d63ed777c728cb4eccc2d5d79a26e377d12b0604e7bf0e \
+    --hash=sha256:84a9f4f422ec525c314fdf54c23a1e73edfbcec968560943ca2d41cfae623b38
     # via botocore-stubs
 types-python-dateutil==2.9.0.20240316 \
     --hash=sha256:5d2f2e240b86905e40944dd787db6da9263f0deabef1076ddaed797351ec0202 \
diff --git a/requirements/reproducible-mac.txt b/requirements/reproducible-mac.txt
index c064ed51..f5a2d853 100644
--- a/requirements/reproducible-mac.txt
+++ b/requirements/reproducible-mac.txt
@@ -56,25 +56,25 @@ blinker==1.8.2 \
     --hash=sha256:1779309f71bf239144b9399d06ae925637cf6634cf6bd131104184531bf67c01 \
     --hash=sha256:8f77b09d3bf7c795e969e9486f39c2c5e9c39d4ee07424be2bc594ece9642d83
     # via flask
-boto3==1.34.143 \
-    --hash=sha256:0d16832f23e6bd3ae94e35ea8e625529850bfad9baccd426de96ad8f445d8e03 \
-    --hash=sha256:b590ce80c65149194def43ebf0ea1cf0533945502507837389a8d22e3ecbcf05
+boto3==1.34.145 \
+    --hash=sha256:69d5afb7a017d07dd6bdfb680d2912d5d369b3fafa0a45161207d9f393b14d7e \
+    --hash=sha256:ac770fb53dde1743aec56bd8e56b7ee2e2f5ad42a37825968ec4ff8428822640
     # via
     #   aws-sam-cli (setup.py)
     #   aws-sam-translator
-boto3-stubs[apigateway,cloudformation,ecr,iam,kinesis,lambda,s3,schemas,secretsmanager,signer,sqs,stepfunctions,sts,xray]==1.34.143 \
-    --hash=sha256:55f1b549507caac68d1affee2f3d8769a1c993960b562fd014f5840dfce9cd29 \
-    --hash=sha256:7a84989088c160d2f0bd177f6f02632b1e9efac1eb548b8b5cb0cbe3aa8f81a6
+boto3-stubs[apigateway,cloudformation,ecr,iam,kinesis,lambda,s3,schemas,secretsmanager,signer,sqs,stepfunctions,sts,xray]==1.34.145 \
+    --hash=sha256:1303deb7d749a2c9bd5a30830e1d537c1474b70c5f429e3db9af5d3eaa7b9696 \
+    --hash=sha256:2d15ccfc44407baae897d3a531f9eb0826e207d70e92f5268e8703139bec2dba
     # via aws-sam-cli (setup.py)
-botocore==1.34.143 \
-    --hash=sha256:059f032ec05733a836e04e869c5a15534420102f93116f3bc9a5b759b0651caf \
-    --hash=sha256:094aea179e8aaa1bc957ad49cc27d93b189dd3a1f3075d8b0ca7c445a2a88430
+botocore==1.34.145 \
+    --hash=sha256:2e72e262de02adcb0264ac2bac159a28f55dbba8d9e52aa0308773a42950dff5 \
+    --hash=sha256:edf0fb4c02186ae29b76263ac5fda18b0a085d334a310551c9984407cf1079e6
     # via
     #   boto3
     #   s3transfer
-botocore-stubs==1.34.143 \
-    --hash=sha256:2bfd2707c9c6857d21616c6c56ed6efcd335782d36d95217483d902f446420f2 \
-    --hash=sha256:ff6e7ca53dbd73cba8009fd29a99bf27d714936ea24eaffd500ecdcef36c9d5a
+botocore-stubs==1.34.145 \
+    --hash=sha256:4c579734492713c142773c7405cf62b466dfaf5f05371b7510d3cd308b0ba6a5 \
+    --hash=sha256:69bcfc0c81eee294e681dc8049b7ca2a47a8f0510b88c05710f07dba6d0785c7
     # via boto3-stubs
 certifi==2024.7.4 \
     --hash=sha256:5a1e7645bc0ec61a09e26c36f6106dd4cf40c6db3a1fb6352b0244e7fb057c7b \
@@ -465,9 +465,9 @@ mypy-boto3-schemas==1.34.0 \
     --hash=sha256:28c016609dcffc606bd8425ee92894c46e943ab77033e1ae46481f00c39b7f75 \
     --hash=sha256:3b25a71944192b0980c3bb5132deb7c06ee9b88580ed63f257fad97cf3bf2927
     # via boto3-stubs
-mypy-boto3-secretsmanager==1.34.128 \
-    --hash=sha256:7ce9815d116fa1749971691355b1e1c8f462d46e7eaa9d84133b8db96dd3515f \
-    --hash=sha256:ae2b398efa1a32214c3eddb6901efa67cfc24a893b113f549a06bb70bb43b402
+mypy-boto3-secretsmanager==1.34.145 \
+    --hash=sha256:986511caa6626edfed7eb11b63c929801e9468c58e15927dc6fc0339c4eb34cb \
+    --hash=sha256:e5a82c05cce68168a3709e5f0d35066cf250961db1d8670f0111da66206814c7
     # via boto3-stubs
 mypy-boto3-signer==1.34.95 \
     --hash=sha256:0118854f1664f7e27e03f5ce2fea3344cc4985fa6ed15a0d5c7a7146b4a4326f \
@@ -958,9 +958,9 @@ tomlkit==0.13.0 \
     --hash=sha256:08ad192699734149f5b97b45f1f18dad7eb1b6d16bc72ad0c2335772650d7b72 \
     --hash=sha256:7075d3042d03b80f603482d69bf0c8f345c2b30e41699fd8883227f89972b264
     # via aws-sam-cli (setup.py)
-types-awscrt==0.21.0 \
-    --hash=sha256:026f882d4d23f04c5b2ab08d6fefd627842537009cd00e9f78dd4960314d51aa \
-    --hash=sha256:06aa247fe5ccf0b86428e5289aeabf67f967e10861f211c16c19e7d2542a70a9
+types-awscrt==0.21.2 \
+    --hash=sha256:0839fe12f0f914d8f7d63ed777c728cb4eccc2d5d79a26e377d12b0604e7bf0e \
+    --hash=sha256:84a9f4f422ec525c314fdf54c23a1e73edfbcec968560943ca2d41cfae623b38
     # via botocore-stubs
 types-python-dateutil==2.9.0.20240316 \
     --hash=sha256:5d2f2e240b86905e40944dd787db6da9263f0deabef1076ddaed797351ec0202 \
diff --git a/requirements/reproducible-win.txt b/requirements/reproducible-win.txt
index 8ba73c18..f4f54703 100644
--- a/requirements/reproducible-win.txt
+++ b/requirements/reproducible-win.txt
@@ -38,25 +38,25 @@ blinker==1.8.2 \
     --hash=sha256:1779309f71bf239144b9399d06ae925637cf6634cf6bd131104184531bf67c01 \
     --hash=sha256:8f77b09d3bf7c795e969e9486f39c2c5e9c39d4ee07424be2bc594ece9642d83
     # via flask
-boto3==1.34.143 \
-    --hash=sha256:0d16832f23e6bd3ae94e35ea8e625529850bfad9baccd426de96ad8f445d8e03 \
-    --hash=sha256:b590ce80c65149194def43ebf0ea1cf0533945502507837389a8d22e3ecbcf05
+boto3==1.34.145 \
+    --hash=sha256:69d5afb7a017d07dd6bdfb680d2912d5d369b3fafa0a45161207d9f393b14d7e \
+    --hash=sha256:ac770fb53dde1743aec56bd8e56b7ee2e2f5ad42a37825968ec4ff8428822640
     # via
     #   aws-sam-cli (setup.py)
     #   aws-sam-translator
-boto3-stubs[apigateway,cloudformation,ecr,iam,kinesis,lambda,s3,schemas,secretsmanager,signer,sqs,stepfunctions,sts,xray]==1.34.143 \
-    --hash=sha256:55f1b549507caac68d1affee2f3d8769a1c993960b562fd014f5840dfce9cd29 \
-    --hash=sha256:7a84989088c160d2f0bd177f6f02632b1e9efac1eb548b8b5cb0cbe3aa8f81a6
+boto3-stubs[apigateway,cloudformation,ecr,iam,kinesis,lambda,s3,schemas,secretsmanager,signer,sqs,stepfunctions,sts,xray]==1.34.145 \
+    --hash=sha256:1303deb7d749a2c9bd5a30830e1d537c1474b70c5f429e3db9af5d3eaa7b9696 \
+    --hash=sha256:2d15ccfc44407baae897d3a531f9eb0826e207d70e92f5268e8703139bec2dba
     # via aws-sam-cli (setup.py)
-botocore==1.34.143 \
-    --hash=sha256:059f032ec05733a836e04e869c5a15534420102f93116f3bc9a5b759b0651caf \
-    --hash=sha256:094aea179e8aaa1bc957ad49cc27d93b189dd3a1f3075d8b0ca7c445a2a88430
+botocore==1.34.145 \
+    --hash=sha256:2e72e262de02adcb0264ac2bac159a28f55dbba8d9e52aa0308773a42950dff5 \
+    --hash=sha256:edf0fb4c02186ae29b76263ac5fda18b0a085d334a310551c9984407cf1079e6
     # via
     #   boto3
     #   s3transfer
-botocore-stubs==1.34.143 \
-    --hash=sha256:2bfd2707c9c6857d21616c6c56ed6efcd335782d36d95217483d902f446420f2 \
-    --hash=sha256:ff6e7ca53dbd73cba8009fd29a99bf27d714936ea24eaffd500ecdcef36c9d5a
+botocore-stubs==1.34.145 \
+    --hash=sha256:4c579734492713c142773c7405cf62b466dfaf5f05371b7510d3cd308b0ba6a5 \
+    --hash=sha256:69bcfc0c81eee294e681dc8049b7ca2a47a8f0510b88c05710f07dba6d0785c7
     # via boto3-stubs
 certifi==2024.7.4 \
     --hash=sha256:5a1e7645bc0ec61a09e26c36f6106dd4cf40c6db3a1fb6352b0244e7fb057c7b \
@@ -441,9 +441,9 @@ mypy-boto3-schemas==1.34.0 \
     --hash=sha256:28c016609dcffc606bd8425ee92894c46e943ab77033e1ae46481f00c39b7f75 \
     --hash=sha256:3b25a71944192b0980c3bb5132deb7c06ee9b88580ed63f257fad97cf3bf2927
     # via boto3-stubs
-mypy-boto3-secretsmanager==1.34.128 \
-    --hash=sha256:7ce9815d116fa1749971691355b1e1c8f462d46e7eaa9d84133b8db96dd3515f \
-    --hash=sha256:ae2b398efa1a32214c3eddb6901efa67cfc24a893b113f549a06bb70bb43b402
+mypy-boto3-secretsmanager==1.34.145 \
+    --hash=sha256:986511caa6626edfed7eb11b63c929801e9468c58e15927dc6fc0339c4eb34cb \
+    --hash=sha256:e5a82c05cce68168a3709e5f0d35066cf250961db1d8670f0111da66206814c7
     # via boto3-stubs
 mypy-boto3-signer==1.34.95 \
     --hash=sha256:0118854f1664f7e27e03f5ce2fea3344cc4985fa6ed15a0d5c7a7146b4a4326f \
@@ -946,9 +946,9 @@ tomlkit==0.13.0 \
     --hash=sha256:08ad192699734149f5b97b45f1f18dad7eb1b6d16bc72ad0c2335772650d7b72 \
     --hash=sha256:7075d3042d03b80f603482d69bf0c8f345c2b30e41699fd8883227f89972b264
     # via aws-sam-cli (setup.py)
-types-awscrt==0.21.0 \
-    --hash=sha256:026f882d4d23f04c5b2ab08d6fefd627842537009cd00e9f78dd4960314d51aa \
-    --hash=sha256:06aa247fe5ccf0b86428e5289aeabf67f967e10861f211c16c19e7d2542a70a9
+types-awscrt==0.21.2 \
+    --hash=sha256:0839fe12f0f914d8f7d63ed777c728cb4eccc2d5d79a26e377d12b0604e7bf0e \
+    --hash=sha256:84a9f4f422ec525c314fdf54c23a1e73edfbcec968560943ca2d41cfae623b38
     # via botocore-stubs
 types-python-dateutil==2.9.0.20240316 \
     --hash=sha256:5d2f2e240b86905e40944dd787db6da9263f0deabef1076ddaed797351ec0202 \
diff --git a/samcli/commands/deploy/command.py b/samcli/commands/deploy/command.py
index 1ec7f8f6..82b01d12 100644
--- a/samcli/commands/deploy/command.py
+++ b/samcli/commands/deploy/command.py
@@ -135,6 +135,12 @@ LOG = logging.getLogger(__name__)
     cls=ClickMutex,
     incompatible_params=["disable_rollback", "no_disable_rollback"],
 )
+@click.option(
+    "--max-wait-duration",
+    default=60,
+    type=int,
+    help="Maximum duration in minutes to wait for the deployment to complete.",
+)
 @stack_name_option(callback=guided_deploy_stack_name)  # pylint: disable=E1120
 @s3_bucket_option(disable_callback=True)  # pylint: disable=E1120
 @image_repository_option
@@ -193,6 +199,7 @@ def cli(
     config_env,
     disable_rollback,
     on_failure,
+    max_wait_duration,
 ):
     """
     `sam deploy` command entry point
@@ -228,6 +235,7 @@ def cli(
         resolve_image_repos,
         disable_rollback,
         on_failure,
+        max_wait_duration,
     )  # pragma: no cover
 
 
@@ -261,6 +269,7 @@ def do_cli(
     resolve_image_repos,
     disable_rollback,
     on_failure,
+    max_wait_duration,
 ):
     """
     Implementation of the ``cli`` method
@@ -368,5 +377,6 @@ def do_cli(
             disable_rollback=guided_context.disable_rollback if guided else disable_rollback,
             poll_delay=poll_delay,
             on_failure=on_failure,
+            max_wait_duration=max_wait_duration,
         ) as deploy_context:
             deploy_context.run()
diff --git a/samcli/commands/deploy/core/options.py b/samcli/commands/deploy/core/options.py
index f7e94336..44503368 100644
--- a/samcli/commands/deploy/core/options.py
+++ b/samcli/commands/deploy/core/options.py
@@ -37,6 +37,7 @@ DEPLOYMENT_OPTIONS: List[str] = [
     "disable_rollback",
     "on_failure",
     "force_upload",
+    "max_wait_duration",
 ]
 
 CONFIGURATION_OPTION_NAMES: List[str] = ["config_env", "config_file"] + SAVE_PARAMS_OPTIONS
diff --git a/samcli/commands/deploy/deploy_context.py b/samcli/commands/deploy/deploy_context.py
index dfd971be..33ac1711 100644
--- a/samcli/commands/deploy/deploy_context.py
+++ b/samcli/commands/deploy/deploy_context.py
@@ -74,6 +74,7 @@ class DeployContext:
         disable_rollback,
         poll_delay,
         on_failure,
+        max_wait_duration,
     ):
         self.template_file = template_file
         self.stack_name = stack_name
@@ -106,6 +107,7 @@ class DeployContext:
         self.poll_delay = poll_delay
         self.on_failure = FailureMode(on_failure) if on_failure else FailureMode.ROLLBACK
         self._max_template_size = 51200
+        self.max_wait_duration = max_wait_duration
 
     def __enter__(self):
         return self
@@ -270,7 +272,7 @@ class DeployContext:
                 marker_time = self.deployer.get_last_event_time(stack_name, 0)
                 self.deployer.execute_changeset(result["Id"], stack_name, disable_rollback)
                 self.deployer.wait_for_execute(
-                    stack_name, changeset_type, disable_rollback, self.on_failure, marker_time
+                    stack_name, changeset_type, disable_rollback, self.on_failure, marker_time, self.max_wait_duration
                 )
                 click.echo(self.MSG_EXECUTE_SUCCESS.format(stack_name=stack_name, region=region))
 
diff --git a/samcli/commands/sync/command.py b/samcli/commands/sync/command.py
index 8d4773f3..397664d2 100644
--- a/samcli/commands/sync/command.py
+++ b/samcli/commands/sync/command.py
@@ -400,6 +400,7 @@ def do_cli(
                     disable_rollback=False,
                     poll_delay=poll_delay,
                     on_failure=None,
+                    max_wait_duration=60,
                 ) as deploy_context:
                     with SyncContext(
                         dependency_layer, build_context.build_dir, build_context.cache_dir, skip_deploy_sync
diff --git a/samcli/lib/deploy/deployer.py b/samcli/lib/deploy/deployer.py
index c1786f3c..522bc04f 100644
--- a/samcli/lib/deploy/deployer.py
+++ b/samcli/lib/deploy/deployer.py
@@ -491,6 +491,7 @@ class Deployer:
         disable_rollback: bool,
         on_failure: FailureMode = FailureMode.ROLLBACK,
         time_stamp_marker: float = 0,
+        max_wait_duration: int = 60,
     ) -> None:
         """
         Wait for stack operation to execute and return when execution completes.
@@ -508,6 +509,8 @@ class Deployer:
             The action to take when the operation fails
         time_stamp_marker:
             last event time on the stack to start streaming events from.
+        max_wait_duration:
+            The maximum duration in minutes to wait for the deployment to complete.
         """
         sys.stdout.write(
             "\n{} - Waiting for stack create/update "
@@ -525,9 +528,12 @@ class Deployer:
         else:
             raise RuntimeError("Invalid stack operation type {0}".format(stack_operation))
 
+        delay = 30
+        max_attempts = max_wait_duration * 60 // delay
+
         # Poll every 30 seconds. Polling too frequently risks hitting rate limits
         # on CloudFormation's DescribeStacks API
-        waiter_config = {"Delay": 30, "MaxAttempts": 120}
+        waiter_config = {"Delay": delay, "MaxAttempts": max_attempts}
 
         try:
             waiter.wait(StackName=stack_name, WaiterConfig=waiter_config)
diff --git a/schema/samcli.json b/schema/samcli.json
index 3dd71b44..1831618a 100644
--- a/schema/samcli.json
+++ b/schema/samcli.json
@@ -1080,7 +1080,7 @@
           "properties": {
             "parameters": {
               "title": "Parameters for the deploy command",
-              "description": "Available parameters for the deploy command:\n* guided:\nSpecify this flag to allow SAM CLI to guide you through the deployment using guided prompts.\n* template_file:\nAWS SAM template which references built artifacts for resources in the template. (if applicable)\n* no_execute_changeset:\nIndicates whether to execute the change set. Specify this flag to view stack changes before executing the change set.\n* fail_on_empty_changeset:\nSpecify whether AWS SAM CLI should return a non-zero exit code if there are no changes to be made to the stack. Defaults to a non-zero exit code.\n* confirm_changeset:\nPrompt to confirm if the computed changeset is to be deployed by SAM CLI.\n* disable_rollback:\nPreserves the state of previously provisioned resources when an operation fails.\n* on_failure:\nProvide an action to determine what will happen when a stack fails to create. Three actions are available:\n\n- ROLLBACK: This will rollback a stack to a previous known good state.\n\n- DELETE: The stack will rollback to a previous state if one exists, otherwise the stack will be deleted.\n\n- DO_NOTHING: The stack will not rollback or delete, this is the same as disabling rollback.\n\nDefault behaviour is ROLLBACK.\n\n\n\nThis option is mutually exclusive with --disable-rollback/--no-disable-rollback. You can provide\n--on-failure or --disable-rollback/--no-disable-rollback but not both at the same time.\n* stack_name:\nName of the AWS CloudFormation stack.\n* s3_bucket:\nAWS S3 bucket where artifacts referenced in the template are uploaded.\n* image_repository:\nAWS ECR repository URI where artifacts referenced in the template are uploaded.\n* image_repositories:\nMapping of Function Logical ID to AWS ECR Repository URI.\n\nExample: Function_Logical_ID=ECR_Repo_Uri\nThis option can be specified multiple times.\n* force_upload:\nIndicates whether to override existing files in the S3 bucket. Specify this flag to upload artifacts even if they match existing artifacts in the S3 bucket.\n* s3_prefix:\nPrefix name that is added to the artifact's name when it is uploaded to the AWS S3 bucket.\n* kms_key_id:\nThe ID of an AWS KMS key that is used to encrypt artifacts that are at rest in the AWS S3 bucket.\n* role_arn:\nARN of an IAM role that AWS Cloudformation assumes when executing a deployment change set.\n* use_json:\nIndicates whether to use JSON as the format for the output AWS CloudFormation template. YAML is used by default.\n* resolve_s3:\nAutomatically resolve AWS S3 bucket for non-guided deployments. Enabling this option will also create a managed default AWS S3 bucket for you. If one does not provide a --s3-bucket value, the managed bucket will be used. Do not use --guided with this option.\n* resolve_image_repos:\nAutomatically create and delete ECR repositories for image-based functions in non-guided deployments. A companion stack containing ECR repos for each function will be deployed along with the template stack. Automatically created image repositories will be deleted if the corresponding functions are removed.\n* metadata:\nMap of metadata to attach to ALL the artifacts that are referenced in the template.\n* notification_arns:\nARNs of SNS topics that AWS Cloudformation associates with the stack.\n* tags:\nList of tags to associate with the stack.\n* parameter_overrides:\nString that contains AWS CloudFormation parameter overrides encoded as key=value pairs.\n* signing_profiles:\nA string that contains Code Sign configuration parameters as FunctionOrLayerNameToSign=SigningProfileName:SigningProfileOwner Since signing profile owner is optional, it could also be written as FunctionOrLayerNameToSign=SigningProfileName\n* no_progressbar:\nDoes not showcase a progress bar when uploading artifacts to S3 and pushing docker images to ECR\n* capabilities:\nList of capabilities that one must specify before AWS Cloudformation can create certain stacks.\n\nAccepted Values: CAPABILITY_IAM, CAPABILITY_NAMED_IAM, CAPABILITY_RESOURCE_POLICY, CAPABILITY_AUTO_EXPAND.\n\nLearn more at: https://docs.aws.amazon.com/serverlessrepo/latest/devguide/acknowledging-application-capabilities.html\n* profile:\nSelect a specific profile from your credential file to get AWS credentials.\n* region:\nSet the AWS Region of the service. (e.g. us-east-1)\n* beta_features:\nEnable/Disable beta features.\n* debug:\nTurn on debug logging to print debug message generated by AWS SAM CLI and display timestamps.\n* save_params:\nSave the parameters provided via the command line to the configuration file.",
+              "description": "Available parameters for the deploy command:\n* guided:\nSpecify this flag to allow SAM CLI to guide you through the deployment using guided prompts.\n* template_file:\nAWS SAM template which references built artifacts for resources in the template. (if applicable)\n* no_execute_changeset:\nIndicates whether to execute the change set. Specify this flag to view stack changes before executing the change set.\n* fail_on_empty_changeset:\nSpecify whether AWS SAM CLI should return a non-zero exit code if there are no changes to be made to the stack. Defaults to a non-zero exit code.\n* confirm_changeset:\nPrompt to confirm if the computed changeset is to be deployed by SAM CLI.\n* disable_rollback:\nPreserves the state of previously provisioned resources when an operation fails.\n* on_failure:\nProvide an action to determine what will happen when a stack fails to create. Three actions are available:\n\n- ROLLBACK: This will rollback a stack to a previous known good state.\n\n- DELETE: The stack will rollback to a previous state if one exists, otherwise the stack will be deleted.\n\n- DO_NOTHING: The stack will not rollback or delete, this is the same as disabling rollback.\n\nDefault behaviour is ROLLBACK.\n\n\n\nThis option is mutually exclusive with --disable-rollback/--no-disable-rollback. You can provide\n--on-failure or --disable-rollback/--no-disable-rollback but not both at the same time.\n* max_wait_duration:\nMaximum duration in minutes to wait for the deployment to complete.\n* stack_name:\nName of the AWS CloudFormation stack.\n* s3_bucket:\nAWS S3 bucket where artifacts referenced in the template are uploaded.\n* image_repository:\nAWS ECR repository URI where artifacts referenced in the template are uploaded.\n* image_repositories:\nMapping of Function Logical ID to AWS ECR Repository URI.\n\nExample: Function_Logical_ID=ECR_Repo_Uri\nThis option can be specified multiple times.\n* force_upload:\nIndicates whether to override existing files in the S3 bucket. Specify this flag to upload artifacts even if they match existing artifacts in the S3 bucket.\n* s3_prefix:\nPrefix name that is added to the artifact's name when it is uploaded to the AWS S3 bucket.\n* kms_key_id:\nThe ID of an AWS KMS key that is used to encrypt artifacts that are at rest in the AWS S3 bucket.\n* role_arn:\nARN of an IAM role that AWS Cloudformation assumes when executing a deployment change set.\n* use_json:\nIndicates whether to use JSON as the format for the output AWS CloudFormation template. YAML is used by default.\n* resolve_s3:\nAutomatically resolve AWS S3 bucket for non-guided deployments. Enabling this option will also create a managed default AWS S3 bucket for you. If one does not provide a --s3-bucket value, the managed bucket will be used. Do not use --guided with this option.\n* resolve_image_repos:\nAutomatically create and delete ECR repositories for image-based functions in non-guided deployments. A companion stack containing ECR repos for each function will be deployed along with the template stack. Automatically created image repositories will be deleted if the corresponding functions are removed.\n* metadata:\nMap of metadata to attach to ALL the artifacts that are referenced in the template.\n* notification_arns:\nARNs of SNS topics that AWS Cloudformation associates with the stack.\n* tags:\nList of tags to associate with the stack.\n* parameter_overrides:\nString that contains AWS CloudFormation parameter overrides encoded as key=value pairs.\n* signing_profiles:\nA string that contains Code Sign configuration parameters as FunctionOrLayerNameToSign=SigningProfileName:SigningProfileOwner Since signing profile owner is optional, it could also be written as FunctionOrLayerNameToSign=SigningProfileName\n* no_progressbar:\nDoes not showcase a progress bar when uploading artifacts to S3 and pushing docker images to ECR\n* capabilities:\nList of capabilities that one must specify before AWS Cloudformation can create certain stacks.\n\nAccepted Values: CAPABILITY_IAM, CAPABILITY_NAMED_IAM, CAPABILITY_RESOURCE_POLICY, CAPABILITY_AUTO_EXPAND.\n\nLearn more at: https://docs.aws.amazon.com/serverlessrepo/latest/devguide/acknowledging-application-capabilities.html\n* profile:\nSelect a specific profile from your credential file to get AWS credentials.\n* region:\nSet the AWS Region of the service. (e.g. us-east-1)\n* beta_features:\nEnable/Disable beta features.\n* debug:\nTurn on debug logging to print debug message generated by AWS SAM CLI and display timestamps.\n* save_params:\nSave the parameters provided via the command line to the configuration file.",
               "type": "object",
               "properties": {
                 "guided": {
@@ -1126,6 +1126,12 @@
                     "ROLLBACK"
                   ]
                 },
+                "max_wait_duration": {
+                  "title": "max_wait_duration",
+                  "type": "integer",
+                  "description": "Maximum duration in minutes to wait for the deployment to complete.",
+                  "default": 60
+                },
                 "stack_name": {
                   "title": "stack_name",
                   "type": "string",


Test patch:
diff --git a/tests/unit/commands/deploy/test_command.py b/tests/unit/commands/deploy/test_command.py
index 3ff809a1..286eb20d 100644
--- a/tests/unit/commands/deploy/test_command.py
+++ b/tests/unit/commands/deploy/test_command.py
@@ -51,6 +51,7 @@ class TestDeployCliCommand(TestCase):
         self.resolve_image_repos = False
         self.disable_rollback = False
         self.on_failure = None
+        self.max_wait_duration = 480
         MOCK_SAM_CONFIG.reset_mock()
 
         self.companion_stack_manager_patch = patch("samcli.commands.deploy.guided_context.CompanionStackManager")
@@ -103,6 +104,7 @@ class TestDeployCliCommand(TestCase):
             resolve_image_repos=self.resolve_image_repos,
             disable_rollback=self.disable_rollback,
             on_failure=self.on_failure,
+            max_wait_duration=self.max_wait_duration,
         )
 
         mock_deploy_context.assert_called_with(
@@ -130,6 +132,7 @@ class TestDeployCliCommand(TestCase):
             disable_rollback=self.disable_rollback,
             poll_delay=os.getenv("SAM_CLI_POLL_DELAY"),
             on_failure=self.on_failure,
+            max_wait_duration=self.max_wait_duration,
         )
 
         context_mock.run.assert_called_with()
@@ -219,6 +222,7 @@ class TestDeployCliCommand(TestCase):
                     resolve_image_repos=self.resolve_image_repos,
                     disable_rollback=self.disable_rollback,
                     on_failure=self.on_failure,
+                    max_wait_duration=self.max_wait_duration,
                 )
 
     @patch("samcli.commands.package.command.click")
@@ -320,6 +324,7 @@ class TestDeployCliCommand(TestCase):
                 resolve_image_repos=self.resolve_image_repos,
                 disable_rollback=self.disable_rollback,
                 on_failure=self.on_failure,
+                max_wait_duration=self.max_wait_duration,
             )
 
             mock_deploy_context.assert_called_with(
@@ -347,6 +352,7 @@ class TestDeployCliCommand(TestCase):
                 disable_rollback=True,
                 poll_delay=5,
                 on_failure=self.on_failure,
+                max_wait_duration=self.max_wait_duration,
             )
 
             context_mock.run.assert_called_with()
@@ -464,6 +470,7 @@ class TestDeployCliCommand(TestCase):
                 resolve_image_repos=self.resolve_image_repos,
                 disable_rollback=self.disable_rollback,
                 on_failure=self.on_failure,
+                max_wait_duration=self.max_wait_duration,
             )
 
             mock_deploy_context.assert_called_with(
@@ -491,6 +498,7 @@ class TestDeployCliCommand(TestCase):
                 disable_rollback=True,
                 poll_delay=5,
                 on_failure=self.on_failure,
+                max_wait_duration=self.max_wait_duration,
             )
 
             context_mock.run.assert_called_with()
@@ -611,6 +619,7 @@ class TestDeployCliCommand(TestCase):
             resolve_image_repos=self.resolve_image_repos,
             disable_rollback=self.disable_rollback,
             on_failure=self.on_failure,
+            max_wait_duration=self.max_wait_duration,
         )
 
         mock_deploy_context.assert_called_with(
@@ -642,6 +651,7 @@ class TestDeployCliCommand(TestCase):
             disable_rollback=True,
             poll_delay=5,
             on_failure=self.on_failure,
+            max_wait_duration=self.max_wait_duration,
         )
 
         context_mock.run.assert_called_with()
@@ -769,6 +779,7 @@ class TestDeployCliCommand(TestCase):
             resolve_image_repos=self.resolve_image_repos,
             disable_rollback=self.disable_rollback,
             on_failure=self.on_failure,
+            max_wait_duration=self.max_wait_duration,
         )
 
         mock_deploy_context.assert_called_with(
@@ -796,6 +807,7 @@ class TestDeployCliCommand(TestCase):
             disable_rollback=True,
             poll_delay=5,
             on_failure=self.on_failure,
+            max_wait_duration=self.max_wait_duration,
         )
 
         context_mock.run.assert_called_with()
@@ -907,6 +919,7 @@ class TestDeployCliCommand(TestCase):
                 resolve_image_repos=self.resolve_image_repos,
                 disable_rollback=self.disable_rollback,
                 on_failure=self.on_failure,
+                max_wait_duration=self.max_wait_duration,
             )
 
             mock_deploy_context.assert_called_with(
@@ -934,6 +947,7 @@ class TestDeployCliCommand(TestCase):
                 disable_rollback=self.disable_rollback,
                 poll_delay=5,
                 on_failure=self.on_failure,
+                max_wait_duration=self.max_wait_duration,
             )
 
             context_mock.run.assert_called_with()
@@ -983,6 +997,7 @@ class TestDeployCliCommand(TestCase):
             resolve_image_repos=self.resolve_image_repos,
             disable_rollback=self.disable_rollback,
             on_failure=self.on_failure,
+            max_wait_duration=self.max_wait_duration,
         )
 
         mock_deploy_context.assert_called_with(
@@ -1010,6 +1025,7 @@ class TestDeployCliCommand(TestCase):
             disable_rollback=self.disable_rollback,
             poll_delay=5,
             on_failure=self.on_failure,
+            max_wait_duration=self.max_wait_duration,
         )
 
         context_mock.run.assert_called_with()
@@ -1047,6 +1063,7 @@ class TestDeployCliCommand(TestCase):
                 resolve_image_repos=self.resolve_image_repos,
                 disable_rollback=self.disable_rollback,
                 on_failure=self.on_failure,
+                max_wait_duration=self.max_wait_duration,
             )
 
     @patch("samcli.commands.package.command.click")
@@ -1098,6 +1115,7 @@ class TestDeployCliCommand(TestCase):
             resolve_image_repos=True,
             disable_rollback=self.disable_rollback,
             on_failure=self.on_failure,
+            max_wait_duration=self.max_wait_duration,
         )
 
         mock_deploy_context.assert_called_with(
@@ -1125,6 +1143,7 @@ class TestDeployCliCommand(TestCase):
             disable_rollback=self.disable_rollback,
             poll_delay=5,
             on_failure=self.on_failure,
+            max_wait_duration=self.max_wait_duration,
         )
 
         context_mock.run.assert_called_with()
@@ -1171,6 +1190,7 @@ class TestDeployCliCommand(TestCase):
             resolve_image_repos=self.resolve_image_repos,
             disable_rollback=self.disable_rollback,
             on_failure=self.on_failure,
+            max_wait_duration=self.max_wait_duration,
         )
 
         mock_deploy_context.assert_called_with(
@@ -1198,6 +1218,7 @@ class TestDeployCliCommand(TestCase):
             disable_rollback=self.disable_rollback,
             poll_delay=os.getenv("SAM_CLI_POLL_DELAY"),
             on_failure=self.on_failure,
+            max_wait_duration=self.max_wait_duration,
         )
 
         mock_package_context.assert_called_with(
diff --git a/tests/unit/commands/deploy/test_deploy_context.py b/tests/unit/commands/deploy/test_deploy_context.py
index 55dff41c..179687a9 100644
--- a/tests/unit/commands/deploy/test_deploy_context.py
+++ b/tests/unit/commands/deploy/test_deploy_context.py
@@ -38,6 +38,7 @@ class TestSamDeployCommand(TestCase):
             disable_rollback=False,
             poll_delay=0.5,
             on_failure=None,
+            max_wait_duration=60,
         )
 
     @patch("boto3.client")
@@ -202,6 +203,7 @@ class TestSamDeployCommand(TestCase):
             disable_rollback=False,
             poll_delay=0.5,
             on_failure=None,
+            max_wait_duration=60,
         )
         patched_get_buildable_stacks.return_value = (Mock(), [])
         patched_auth_required.return_value = [("HelloWorldFunction", False)]
@@ -271,5 +273,5 @@ class TestSamDeployCommand(TestCase):
             self.deploy_command_context.run()
 
             self.deploy_command_context.deployer.wait_for_execute.assert_called_with(
-                ANY, "CREATE", False, FailureMode.DO_NOTHING, 1000
+                ANY, "CREATE", False, FailureMode.DO_NOTHING, 1000, 60
             )
diff --git a/tests/unit/commands/samconfig/test_samconfig.py b/tests/unit/commands/samconfig/test_samconfig.py
index c161cb3a..92ccbd93 100644
--- a/tests/unit/commands/samconfig/test_samconfig.py
+++ b/tests/unit/commands/samconfig/test_samconfig.py
@@ -694,6 +694,7 @@ class TestSamConfigForAllCommands(TestCase):
                 False,
                 True,
                 "ROLLBACK",
+                60,
             )
 
     @patch("samcli.commands.deploy.command.do_cli")
@@ -808,6 +809,7 @@ class TestSamConfigForAllCommands(TestCase):
                 False,
                 True,
                 "ROLLBACK",
+                60,
             )
 
     @patch("samcli.commands._utils.experimental.is_experimental_enabled")
diff --git a/tests/unit/commands/sync/test_command.py b/tests/unit/commands/sync/test_command.py
index 94ab8a45..442bf63e 100644
--- a/tests/unit/commands/sync/test_command.py
+++ b/tests/unit/commands/sync/test_command.py
@@ -217,6 +217,7 @@ class TestDoCli(TestCase):
             disable_rollback=False,
             poll_delay=10,
             on_failure=None,
+            max_wait_duration=60,
         )
 
         execute_infra_mock.assert_called_with(
@@ -375,6 +376,7 @@ class TestDoCli(TestCase):
             disable_rollback=False,
             poll_delay=0.5,
             on_failure=None,
+            max_wait_duration=60,
         )
         execute_watch_mock.assert_called_once_with(
             template=self.template_file,