diff --git a/slingshot/cli.py b/slingshot/cli.py
index 047d98e..75ad0c9 100644
--- a/slingshot/cli.py
+++ b/slingshot/cli.py
@@ -28,7 +28,9 @@ def main():
 @click.option('--username', help="Username for kepler submission.")
 @click.option('--password',
               help="Password for kepler submission. Omit for prompt.")
-def run(layers, store, url, namespace, username, password):
+@click.option('--fail-after', default=5,
+              help="Stop after number of consecutive failures. Default is 5.")
+def run(layers, store, url, namespace, username, password, fail_after):
     """Create and upload bags to the specified endpoint.
 
     This script will create bags from all the layers in the LAYERS
@@ -50,6 +52,7 @@ def run(layers, store, url, namespace, username, password):
     auth = username, password
     if not all(auth):
         auth = None
+    failures = 0
     for data_layer in uploadable(layers, store):
         bag = prep_bag(os.path.join(layers, data_layer), store)
         try:
@@ -57,10 +60,15 @@ def run(layers, store, url, namespace, username, password):
             bag_name = make_uuid(os.path.basename(bag), namespace)
             with temp_archive(bag, bag_name) as zf:
                 submit(zf, url, auth)
+            click.echo("%sZ: %s uploaded" % (datetime.utcnow().isoformat(),
+                                             data_layer))
+            failures = 0
         except Exception as e:
             shutil.rmtree(bag, ignore_errors=True)
+            failures += 1
             click.echo("%sZ: %s failed with %r" %
                        (datetime.utcnow().isoformat(), data_layer, e))
-            raise e
-        click.echo("%sZ: %s uploaded" % (datetime.utcnow().isoformat(),
-                                         data_layer))
+            if failures >= fail_after:
+                click.echo("%sZ: Maximum number of consecutive failures (%d)" %
+                           (datetime.utcnow().isoformat(), failures))
+                raise e

Set script failure conditions
The script should not completely fail for one bad layer, but rather log it and continue. It should fail immediately in certain cases, such as, 404 and 401. It should probably fail after some number of layers have failed consecutively, since that would likely indicate something more fundamental is wrong.
diff --git a/tests/test_cli.py b/tests/test_cli.py
index 61eca94..e32903d 100644
--- a/tests/test_cli.py
+++ b/tests/test_cli.py
@@ -77,3 +77,12 @@ def test_run_logs_failed_layers_to_stdout(runner, layers_dir):
         res = runner.invoke(main, ['run', layers_dir, store,
                                    'http://localhost'])
         assert 'SDE_DATA_BD_A8GNS_2003.zip failed' in res.output
+
+
+def test_run_fails_after_consecutive_failures(runner, layers_dir):
+    with requests_mock.Mocker() as m:
+        store = tempfile.mkdtemp()
+        m.post('http://localhost', status_code=500)
+        res = runner.invoke(main, ['run', layers_dir, store,
+                                   'http://localhost', '--fail-after', 1])
+        assert 'Maximum number of consecutive failures' in res.output

pip install -e .
parse_log_pytest
requirements.txt
pytest mock requests_mock
3.9
requirements.txt
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
bagit==1.5.4
certifi==2025.1.31
charset-normalizer==3.4.1
click==6.3
exceptiongroup==1.2.2
idna==3.10
iniconfig==2.1.0
mock==5.2.0
packaging==24.2
pluggy==1.5.0
pytest==8.3.5
requests==2.32.3
requests-mock==1.12.1
-e git+https://github.com/MITLibraries/slingshot.git@6dca99653150369b2b5b422292acd1e0b1fb131e#egg=slingshot
tomli==2.2.1
urllib3==2.3.0

name: slingshot
channels:
  - defaults
  - https://repo.anaconda.com/pkgs/main
  - https://repo.anaconda.com/pkgs/r
  - conda-forge
dependencies:
  - _libgcc_mutex=0.1=main
  - _openmp_mutex=5.1=1_gnu
  - ca-certificates=2025.2.25=h06a4308_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.4.4=h6a678d5_1
  - libgcc-ng=11.2.0=h1234567_1
  - libgomp=11.2.0=h1234567_1
  - libstdcxx-ng=11.2.0=h1234567_1
  - ncurses=6.4=h6a678d5_0
  - openssl=3.0.16=h5eee18b_0
  - pip=25.0=py39h06a4308_0
  - python=3.9.21=he870216_1
  - readline=8.2=h5eee18b_0
  - setuptools=75.8.0=py39h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - tzdata=2025a=h04d1e81_0
  - xz=5.6.4=h5eee18b_1
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - bagit==1.5.4
      - certifi==2025.1.31
      - charset-normalizer==3.4.1
      - click==6.3
      - exceptiongroup==1.2.2
      - idna==3.10
      - iniconfig==2.1.0
      - mock==5.2.0
      - packaging==24.2
      - pluggy==1.5.0
      - pytest==8.3.5
      - requests==2.32.3
      - requests-mock==1.12.1
      - tomli==2.2.1
      - urllib3==2.3.0
      - wheel==0.26.0
prefix: /opt/conda/envs/slingshot

tests/test_cli.py::test_run_fails_after_consecutive_failures
tests/test_cli.py::test_run_submits_bags tests/test_cli.py::test_run_leaves_bag_on_success tests/test_cli.py::test_run_removes_bag_on_failure tests/test_cli.py::test_run_uses_supplied_namespace tests/test_cli.py::test_run_uses_authentication tests/test_cli.py::test_run_logs_uploaded_layers_to_stdout tests/test_cli.py::test_run_logs_failed_layers_to_stdout
slingshot/cli.py
slingshot/cli.py