checkout, cq_deps: Resolve deps in commit messages

Resolve dependency references in commit messages like below, and only in
the last "paragraph". "ignore:" is added because otherwise CQ won't
process this CL.

ignore:Cq-Depend: gerritname:1, commas-supported:2,spaces-not-required:3
ignore:Cq-Depend: name:4
ignore:Cq-Depend: name:5,,,,extra-commas-ignored:6,,,,

Compare http://go/bbid/8866545454308933312 and led run below for
pwrev/17461. Both builds fail for unrelated reasons, but the led build
applies two changes instead of just one.

$ led get-build 8866545454308933312 | led edit-recipe-bundle | led launch
LUCI UI: https://ci.chromium.org/swarming/task/4f3b6ebae9aabf10?server=chromium-swarm.appspot.com

Change-Id: I493ff86e1cc09fb4221a9faa41786bfbb1441461
Bug: 233
Reviewed-on: https://pigweed-review.googlesource.com/c/infra/recipes/+/20260
Commit-Queue: Rob Mohr <mohrr@google.com>
Reviewed-by: Marc-Antoine Ruel <maruel@google.com>
Reviewed-by: Oliver Newman <olivernewman@google.com>
diff --git a/recipe_modules/checkout/__init__.py b/recipe_modules/checkout/__init__.py
index 67325b2..df16ea3 100644
--- a/recipe_modules/checkout/__init__.py
+++ b/recipe_modules/checkout/__init__.py
@@ -21,6 +21,7 @@
     'fuchsia/git',
     'fuchsia/gitiles',
     'fuchsia/sso',
+    'pigweed/cq_deps',
     'pigweed/repo',
     'recipe_engine/buildbucket',
     'recipe_engine/context',
diff --git a/recipe_modules/checkout/api.py b/recipe_modules/checkout/api.py
index 2dda11c..93bfa02 100644
--- a/recipe_modules/checkout/api.py
+++ b/recipe_modules/checkout/api.py
@@ -221,6 +221,70 @@
 
         return manifest
 
+    def _process_gerrit_change(self, bb_input, change):
+        """Process a LUCI GerritChange and return a _Change object."""
+
+        assert change.host
+        ref = 'refs/changes/{:02}/{}/{}'.format(
+            change.change % 100, change.change, change.patchset,
+        )
+        host = change.host.replace(
+            '-review.googlesource.com', '.googlesource.com'
+        )
+        remote = 'https://{}/{}'.format(host, change.project).strip('/')
+        gerrit_name = host.split('.')[0]
+        branch = self.m.gerrit.change_details(
+            'details',
+            change_id=str(change.change),
+            host=change.host,
+            max_attempts=5,
+            test_data=self.m.json.test_api.output({'branch': 'master'}),
+        ).json.output['branch']
+
+        return _Change(
+            number=change.change,
+            bb_input=bb_input,
+            remote=remote,
+            ref=ref,
+            rebase=True,
+            branch=branch,
+            gerrit_name=gerrit_name,
+        )
+
+    def _process_gerrit_changes(self, bb_input):
+        seen = set()
+        for i, change in enumerate(bb_input.gerrit_changes):
+            with self.m.step.nest(str(i)):
+                result = self._process_gerrit_change(bb_input, change)
+                yield result
+                seen.add(result.name)
+
+        deps, unresolved = self.m.cq_deps.resolve(
+            result.gerrit_name,
+            result.number,
+        )
+        for dep in deps:
+            # dep.name should only appear in seen if there are multiple
+            # gerrit_changes from buildbucket and a later one depends on an
+            # earlier one. If buildbucket has multiple gerrit_changes the
+            # cq_deps module is not needed here, so this is just double-checking
+            # something that shouldn't happen.
+            if dep.name in seen:  # pragma: no cover
+                continue
+            seen.add(dep.name)
+            yield self._process_gerrit_change(bb_input, dep)
+
+        for cl in unresolved:
+            yield _Change(
+                number=cl.change,
+                bb_input=None,
+                remote=None,
+                ref=None,
+                rebase=None,
+                branch=None,
+                gerrit_name=cl.gerrit_name,
+            )
+
     def _change_data(self, remote=None, branch=None):
         bb_input = self.m.buildbucket.build.input
         results = []
@@ -228,41 +292,7 @@
         with self.m.step.nest('change data'):
             if bb_input.gerrit_changes:
                 with self.m.step.nest('process gerrit changes'):
-                    for i, change in enumerate(bb_input.gerrit_changes):
-                        with self.m.step.nest(str(i)):
-                            assert change.host
-                            ref = 'refs/changes/{:02}/{}/{}'.format(
-                                change.change % 100,
-                                change.change,
-                                change.patchset,
-                            )
-                            host = change.host.replace(
-                                '-review.googlesource.com', '.googlesource.com'
-                            )
-                            remote = 'https://{}/{}'.format(
-                                host, change.project
-                            ).strip('/')
-                            gerrit_name = host.split('.')[0]
-                            branch = self.m.gerrit.change_details(
-                                'details',
-                                change_id=str(change.change),
-                                host=change.host,
-                                test_data=self.m.json.test_api.output(
-                                    {'branch': 'master'}
-                                ),
-                            ).json.output['branch']
-
-                            results.append(
-                                _Change(
-                                    number=change.change,
-                                    bb_input=bb_input,
-                                    remote=remote,
-                                    ref=ref,
-                                    rebase=True,
-                                    branch=branch,
-                                    gerrit_name=gerrit_name,
-                                )
-                            )
+                    results.extend(self._process_gerrit_changes(bb_input))
 
             elif bb_input.gitiles_commit.id:
                 with self.m.step.nest('process gitiles commit'):
@@ -278,15 +308,18 @@
                     )
                     gerrit_name = commit.host.split('.')[0]
 
-                    query_results = self.m.gerrit.change_query(
-                        'number',
-                        'commit:{}'.format(commit.id),
-                        host=host,
-                        ok_ret='any',
-                        test_data=self.m.json.test_api.output(
-                            [{'_number': '1234', 'branch': branch}]
-                        ),
-                    ).json.output
+                    try:
+                        query_results = self.m.gerrit.change_query(
+                            'number',
+                            'commit:{}'.format(commit.id),
+                            host=host,
+                            max_attempts=5,
+                            test_data=self.m.json.test_api.output(
+                                [{'_number': '1234', 'branch': branch}]
+                            ),
+                        ).json.output
+                    except self.m.step.StepFailure:  # pragma: no cover
+                        query_results = None
 
                     # Skip adding change if this commit didn't go through
                     # Gerrit.
@@ -423,16 +456,18 @@
                 extra_calls()
 
     def _check_unapplied_changes(self, changes):
+        failed_to_apply = []
         if not changes:
             return  # pragma: no cover
 
-        def display_unapplied_change(change):
+        def handle_unapplied_change(change):
             with self.m.step.nest(
                 'failed to apply {}'.format(change.name)
             ) as pres:
                 pres.status = 'WARNING'
                 pres.links['gerrit'] = change.gerrit_url
                 pres.links['gitiles'] = change.gitiles_url
+            failed_to_apply.append(change)
 
         with self.m.context(infra_steps=True):
             if all(not x.applied for x in changes):
@@ -440,7 +475,7 @@
                     pres.status = 'FAILURE'
                     for change in changes:
                         if not change.applied:
-                            display_unapplied_change(change)
+                            handle_unapplied_change(change)
                     pres.properties['changes'] = [x.name for x in changes]
 
                 raise self.m.step.InfraFailure(
@@ -452,7 +487,9 @@
                     pres.status = 'WARNING'
                     for change in changes:
                         if not change.applied:
-                            display_unapplied_change(change)
+                            handle_unapplied_change(change)
+
+        return failed_to_apply
 
     def _git(self, remote, branch, use_trigger, root, changes):
         """Checkout code from git.
@@ -464,6 +501,10 @@
                 checkout.
             root (Path): Path to checkout into.
             changes (sequence[_Change]): List of triggering changes.
+
+        Returns:
+            List of changes referenced by triggering CL but not applicable to
+            the checkout.
         """
 
         with self.m.context(infra_steps=True):
@@ -472,6 +513,7 @@
             )
 
         submodules = []
+        failed_to_apply = []
 
         with self.m.context(cwd=root):
             if use_trigger:
@@ -505,7 +547,7 @@
                         if submodule.remote == change.remote:
                             self._apply_change(change, cwd=submodule.path)
 
-                self._check_unapplied_changes(changes)
+                failed_to_apply = self._check_unapplied_changes(changes)
 
             # Run git log for both the top-level checkout and every submodule.
             with self.m.step.nest('git log'):
@@ -516,6 +558,8 @@
                             str(submodule.path), 'log', '--oneline', '-n', '10',
                         )
 
+            return failed_to_apply
+
     def _repo(self, remote, branch, manifest_file, use_trigger, root, changes):
         """Checkout code from an Android Repo Tool manifest.
 
@@ -527,6 +571,10 @@
                 checkout.
             root (Path): Path to checkout into.
             changes (sequence[_Change]): List of triggering changes.
+
+        Returns:
+            List of changes referenced by triggering CL but not applicable to
+            the checkout.
         """
 
         # Git makes the top-level folder, Repo requires caller to make it.
@@ -535,6 +583,8 @@
         if manifest_file is None:
             manifest_file = self._manifest_file
 
+        failed_to_apply = []
+
         with self.m.context(cwd=root):
             remote = remote.rstrip('/')
 
@@ -563,7 +613,7 @@
                             ) as pres:
                                 pres.step_summary_text = (
                                     "Can't figure out which manifest branch to "
-                                    'use. Remove some "Cq-Depends:" lines to '
+                                    'use. Remove some "Cq-Depend:" lines to '
                                     'simplify the checkout.'
                                 )
                                 raise self.m.step.StepFailure(
@@ -651,7 +701,7 @@
                                 remote=entry.remote,
                             )
 
-                self._check_unapplied_changes(changes)
+                failed_to_apply = self._check_unapplied_changes(changes)
 
             self._manifest_snapshot = self.m.repo.manifest_snapshot()
 
@@ -672,6 +722,8 @@
             if len(files) == 1:
                 self._root = files.pop()
 
+        return failed_to_apply
+
     def __call__(
         self,
         remote=None,
@@ -728,7 +780,7 @@
         if remote.endswith('.git'):
             remote = remote[0:-4]
 
-        with self.m.step.nest('checkout {}'.format(name)):
+        with self.m.step.nest('checkout {}'.format(name)) as pres:
             changes = self._change_data(remote, branch)
 
             if root is None:
@@ -736,7 +788,7 @@
                 self._changes = changes
 
             if use_repo:
-                self._repo(
+                failed_to_apply = self._repo(
                     remote=remote,
                     branch=branch,
                     root=root,
@@ -745,7 +797,7 @@
                     changes=changes,
                 )
             else:
-                self._git(
+                failed_to_apply = self._git(
                     remote=remote,
                     branch=branch,
                     root=root,
@@ -753,6 +805,11 @@
                     changes=changes,
                 )
 
+            if failed_to_apply:
+                pres.step_summary_text = 'Failed to apply the following CLs'
+                for change in failed_to_apply:
+                    pres.links[change.name] = change.gerrit_url
+
     @property
     def root(self):
         """Returns the logical top level directory of the checkout.
diff --git a/recipe_modules/checkout/test_api.py b/recipe_modules/checkout/test_api.py
index b3bb841..e63e900 100644
--- a/recipe_modules/checkout/test_api.py
+++ b/recipe_modules/checkout/test_api.py
@@ -82,17 +82,12 @@
     def manifest_repo(self):
         return MANIFEST_REPO
 
-    def git_properties(
-        self, remote=REPO, branch='master', use_repo=False, use_repo_cache=False
-    ):
-        return {
-            '$pigweed/checkout': {
-                'remote': remote,
-                'branch': branch,
-                'use_repo': use_repo,
-                'use_repo_cache': use_repo_cache,
-            },
-        }
+    def git_properties(self, **kwargs):
+        kwargs.setdefault('remote', REPO)
+        kwargs.setdefault('branch', 'master')
+        kwargs.setdefault('use_repo', False)
+        kwargs.setdefault('use_repo_cache', False)
+        return {'$pigweed/checkout': dict(**kwargs)}
 
     def repo_properties(self, remote=MANIFEST_REPO, use_repo=True, **kwargs):
         return self.git_properties(remote=remote, use_repo=use_repo, **kwargs)
diff --git a/recipe_modules/checkout/tests/git.expected/try.json b/recipe_modules/checkout/tests/git.expected/try.json
index 833bcc0..3813ac9 100644
--- a/recipe_modules/checkout/tests/git.expected/try.json
+++ b/recipe_modules/checkout/tests/git.expected/try.json
@@ -101,6 +101,57 @@
   },
   {
     "cmd": [],
+    "name": "checkout pigweed.change data.process gerrit changes.resolve CL deps",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@3@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "[CACHE]/cipd/infra/tools/luci/gerrit/latest/gerrit",
+      "change-detail",
+      "-host",
+      "https://pigweed-review.googlesource.com",
+      "-input",
+      "{\"change_id\": \"123456\", \"params\": {\"o\": [\"CURRENT_COMMIT\", \"CURRENT_REVISION\"]}}",
+      "-output",
+      "/path/to/tmp/json"
+    ],
+    "name": "checkout pigweed.change data.process gerrit changes.resolve CL deps.details pigweed:123456",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@4@@@",
+      "@@@STEP_LOG_LINE@json.output@{@@@",
+      "@@@STEP_LOG_LINE@json.output@  \"current_revision\": \"HASH\", @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"project\": \"project\", @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"revisions\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@    \"HASH\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@      \"_number\": 1, @@@",
+      "@@@STEP_LOG_LINE@json.output@      \"commit\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@        \"message\": \"\"@@@",
+      "@@@STEP_LOG_LINE@json.output@      }@@@",
+      "@@@STEP_LOG_LINE@json.output@    }@@@",
+      "@@@STEP_LOG_LINE@json.output@  }, @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"status\": \"NEW\"@@@",
+      "@@@STEP_LOG_LINE@json.output@}@@@",
+      "@@@STEP_LOG_END@json.output@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "checkout pigweed.change data.process gerrit changes.resolve CL deps.resolve deps for pigweed:123456",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@4@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "checkout pigweed.change data.process gerrit changes.resolve CL deps.pass",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@4@@@"
+    ]
+  },
+  {
+    "cmd": [],
     "name": "checkout pigweed.change data.changes",
     "~followup_annotations": [
       "@@@STEP_NEST_LEVEL@2@@@"
diff --git a/recipe_modules/checkout/tests/repo.expected/feature_branches_try.json b/recipe_modules/checkout/tests/repo.expected/feature_branches_try.json
index 2eeafe0..1deb778 100644
--- a/recipe_modules/checkout/tests/repo.expected/feature_branches_try.json
+++ b/recipe_modules/checkout/tests/repo.expected/feature_branches_try.json
@@ -101,6 +101,57 @@
   },
   {
     "cmd": [],
+    "name": "checkout pigweed.change data.process gerrit changes.resolve CL deps",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@3@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "[CACHE]/cipd/infra/tools/luci/gerrit/latest/gerrit",
+      "change-detail",
+      "-host",
+      "https://pigweed-review.googlesource.com",
+      "-input",
+      "{\"change_id\": \"123456\", \"params\": {\"o\": [\"CURRENT_COMMIT\", \"CURRENT_REVISION\"]}}",
+      "-output",
+      "/path/to/tmp/json"
+    ],
+    "name": "checkout pigweed.change data.process gerrit changes.resolve CL deps.details pigweed:123456",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@4@@@",
+      "@@@STEP_LOG_LINE@json.output@{@@@",
+      "@@@STEP_LOG_LINE@json.output@  \"current_revision\": \"HASH\", @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"project\": \"project\", @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"revisions\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@    \"HASH\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@      \"_number\": 1, @@@",
+      "@@@STEP_LOG_LINE@json.output@      \"commit\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@        \"message\": \"\"@@@",
+      "@@@STEP_LOG_LINE@json.output@      }@@@",
+      "@@@STEP_LOG_LINE@json.output@    }@@@",
+      "@@@STEP_LOG_LINE@json.output@  }, @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"status\": \"NEW\"@@@",
+      "@@@STEP_LOG_LINE@json.output@}@@@",
+      "@@@STEP_LOG_END@json.output@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "checkout pigweed.change data.process gerrit changes.resolve CL deps.resolve deps for pigweed:123456",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@4@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "checkout pigweed.change data.process gerrit changes.resolve CL deps.pass",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@4@@@"
+    ]
+  },
+  {
+    "cmd": [],
     "name": "checkout pigweed.change data.changes",
     "~followup_annotations": [
       "@@@STEP_NEST_LEVEL@2@@@"
diff --git a/recipe_modules/checkout/tests/repo.expected/feature_branches_try_multiple_features.json b/recipe_modules/checkout/tests/repo.expected/feature_branches_try_multiple_features.json
index d293293..ba083a8 100644
--- a/recipe_modules/checkout/tests/repo.expected/feature_branches_try_multiple_features.json
+++ b/recipe_modules/checkout/tests/repo.expected/feature_branches_try_multiple_features.json
@@ -128,6 +128,57 @@
   },
   {
     "cmd": [],
+    "name": "checkout pigweed.change data.process gerrit changes.resolve CL deps",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@3@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "[CACHE]/cipd/infra/tools/luci/gerrit/latest/gerrit",
+      "change-detail",
+      "-host",
+      "https://default-review.googlesource.com",
+      "-input",
+      "{\"change_id\": \"2345\", \"params\": {\"o\": [\"CURRENT_COMMIT\", \"CURRENT_REVISION\"]}}",
+      "-output",
+      "/path/to/tmp/json"
+    ],
+    "name": "checkout pigweed.change data.process gerrit changes.resolve CL deps.details default:2345",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@4@@@",
+      "@@@STEP_LOG_LINE@json.output@{@@@",
+      "@@@STEP_LOG_LINE@json.output@  \"current_revision\": \"HASH\", @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"project\": \"project\", @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"revisions\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@    \"HASH\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@      \"_number\": 1, @@@",
+      "@@@STEP_LOG_LINE@json.output@      \"commit\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@        \"message\": \"\"@@@",
+      "@@@STEP_LOG_LINE@json.output@      }@@@",
+      "@@@STEP_LOG_LINE@json.output@    }@@@",
+      "@@@STEP_LOG_LINE@json.output@  }, @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"status\": \"NEW\"@@@",
+      "@@@STEP_LOG_LINE@json.output@}@@@",
+      "@@@STEP_LOG_END@json.output@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "checkout pigweed.change data.process gerrit changes.resolve CL deps.resolve deps for default:2345",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@4@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "checkout pigweed.change data.process gerrit changes.resolve CL deps.pass",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@4@@@"
+    ]
+  },
+  {
+    "cmd": [],
     "name": "checkout pigweed.change data.changes",
     "~followup_annotations": [
       "@@@STEP_NEST_LEVEL@2@@@"
diff --git a/recipe_modules/checkout/tests/repo.expected/feature_branches_try_multiple_matches.json b/recipe_modules/checkout/tests/repo.expected/feature_branches_try_multiple_matches.json
index 0200c43..518433e 100644
--- a/recipe_modules/checkout/tests/repo.expected/feature_branches_try_multiple_matches.json
+++ b/recipe_modules/checkout/tests/repo.expected/feature_branches_try_multiple_matches.json
@@ -131,6 +131,57 @@
   },
   {
     "cmd": [],
+    "name": "checkout pigweed.change data.process gerrit changes.resolve CL deps",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@3@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "[CACHE]/cipd/infra/tools/luci/gerrit/latest/gerrit",
+      "change-detail",
+      "-host",
+      "https://default-review.googlesource.com",
+      "-input",
+      "{\"change_id\": \"2345\", \"params\": {\"o\": [\"CURRENT_COMMIT\", \"CURRENT_REVISION\"]}}",
+      "-output",
+      "/path/to/tmp/json"
+    ],
+    "name": "checkout pigweed.change data.process gerrit changes.resolve CL deps.details default:2345",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@4@@@",
+      "@@@STEP_LOG_LINE@json.output@{@@@",
+      "@@@STEP_LOG_LINE@json.output@  \"current_revision\": \"HASH\", @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"project\": \"project\", @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"revisions\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@    \"HASH\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@      \"_number\": 1, @@@",
+      "@@@STEP_LOG_LINE@json.output@      \"commit\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@        \"message\": \"\"@@@",
+      "@@@STEP_LOG_LINE@json.output@      }@@@",
+      "@@@STEP_LOG_LINE@json.output@    }@@@",
+      "@@@STEP_LOG_LINE@json.output@  }, @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"status\": \"NEW\"@@@",
+      "@@@STEP_LOG_LINE@json.output@}@@@",
+      "@@@STEP_LOG_END@json.output@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "checkout pigweed.change data.process gerrit changes.resolve CL deps.resolve deps for default:2345",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@4@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "checkout pigweed.change data.process gerrit changes.resolve CL deps.pass",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@4@@@"
+    ]
+  },
+  {
+    "cmd": [],
     "name": "checkout pigweed.change data.changes",
     "~followup_annotations": [
       "@@@STEP_NEST_LEVEL@2@@@"
@@ -287,7 +338,7 @@
     "name": "checkout pigweed.too many matching branches (feature2, feature1)",
     "~followup_annotations": [
       "@@@STEP_NEST_LEVEL@1@@@",
-      "@@@STEP_SUMMARY_TEXT@Can't figure out which manifest branch to use. Remove some \"Cq-Depends:\" lines to simplify the checkout.@@@",
+      "@@@STEP_SUMMARY_TEXT@Can't figure out which manifest branch to use. Remove some \"Cq-Depend:\" lines to simplify the checkout.@@@",
       "@@@STEP_FAILURE@@@"
     ]
   },
diff --git a/recipe_modules/checkout/tests/repo.expected/try-multiple-cqdeps.json b/recipe_modules/checkout/tests/repo.expected/try-multiple-cqdeps.json
new file mode 100644
index 0000000..fd7b2ca
--- /dev/null
+++ b/recipe_modules/checkout/tests/repo.expected/try-multiple-cqdeps.json
@@ -0,0 +1,1119 @@
+[
+  {
+    "cmd": [],
+    "name": "checkout pigweed"
+  },
+  {
+    "cmd": [],
+    "name": "checkout pigweed.change data",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "checkout pigweed.change data.process gerrit changes",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "checkout pigweed.change data.process gerrit changes.0",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@3@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "checkout pigweed.change data.process gerrit changes.0.install infra/tools/luci/gerrit",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@4@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "ensure-directory",
+      "--mode",
+      "0777",
+      "[CACHE]/cipd/infra/tools/luci/gerrit/latest"
+    ],
+    "infra_step": true,
+    "name": "checkout pigweed.change data.process gerrit changes.0.install infra/tools/luci/gerrit.ensure package directory",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@5@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "cipd",
+      "ensure",
+      "-root",
+      "[CACHE]/cipd/infra/tools/luci/gerrit/latest",
+      "-ensure-file",
+      "infra/tools/luci/gerrit/${platform} latest",
+      "-max-threads",
+      "0",
+      "-json-output",
+      "/path/to/tmp/json"
+    ],
+    "infra_step": true,
+    "name": "checkout pigweed.change data.process gerrit changes.0.install infra/tools/luci/gerrit.ensure_installed",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@5@@@",
+      "@@@STEP_LOG_LINE@json.output@{@@@",
+      "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@    \"\": [@@@",
+      "@@@STEP_LOG_LINE@json.output@      {@@@",
+      "@@@STEP_LOG_LINE@json.output@        \"instance_id\": \"resolved-instance_id-of-latest----------\", @@@",
+      "@@@STEP_LOG_LINE@json.output@        \"package\": \"infra/tools/luci/gerrit/resolved-platform\"@@@",
+      "@@@STEP_LOG_LINE@json.output@      }@@@",
+      "@@@STEP_LOG_LINE@json.output@    ]@@@",
+      "@@@STEP_LOG_LINE@json.output@  }@@@",
+      "@@@STEP_LOG_LINE@json.output@}@@@",
+      "@@@STEP_LOG_END@json.output@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "[CACHE]/cipd/infra/tools/luci/gerrit/latest/gerrit",
+      "change-detail",
+      "-host",
+      "https://pigweed-review.googlesource.com",
+      "-input",
+      "{\"change_id\": \"1234\"}",
+      "-output",
+      "/path/to/tmp/json"
+    ],
+    "name": "checkout pigweed.change data.process gerrit changes.0.details",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@4@@@",
+      "@@@STEP_LOG_LINE@json.output@{@@@",
+      "@@@STEP_LOG_LINE@json.output@  \"branch\": \"master\"@@@",
+      "@@@STEP_LOG_LINE@json.output@}@@@",
+      "@@@STEP_LOG_END@json.output@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "checkout pigweed.change data.process gerrit changes.resolve CL deps",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@3@@@",
+      "@@@STEP_LINK@default:2345@https://default-review.googlesource.com/2345@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "[CACHE]/cipd/infra/tools/luci/gerrit/latest/gerrit",
+      "change-detail",
+      "-host",
+      "https://pigweed-review.googlesource.com",
+      "-input",
+      "{\"change_id\": \"1234\", \"params\": {\"o\": [\"CURRENT_COMMIT\", \"CURRENT_REVISION\"]}}",
+      "-output",
+      "/path/to/tmp/json"
+    ],
+    "name": "checkout pigweed.change data.process gerrit changes.resolve CL deps.details pigweed:1234",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@4@@@",
+      "@@@STEP_LOG_LINE@json.output@{@@@",
+      "@@@STEP_LOG_LINE@json.output@  \"_number\": 1234, @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"current_revision\": \"HASH\", @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"project\": \"project\", @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"revisions\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@    \"HASH\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@      \"_number\": 1, @@@",
+      "@@@STEP_LOG_LINE@json.output@      \"commit\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@        \"message\": \"Cq-Depend: default:2345\"@@@",
+      "@@@STEP_LOG_LINE@json.output@      }@@@",
+      "@@@STEP_LOG_LINE@json.output@    }@@@",
+      "@@@STEP_LOG_LINE@json.output@  }, @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"status\": \"NEW\"@@@",
+      "@@@STEP_LOG_LINE@json.output@}@@@",
+      "@@@STEP_LOG_END@json.output@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "checkout pigweed.change data.process gerrit changes.resolve CL deps.resolve deps for pigweed:1234",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@4@@@",
+      "@@@STEP_LINK@default:2345@https://default-review.googlesource.com/2345@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "[CACHE]/cipd/infra/tools/luci/gerrit/latest/gerrit",
+      "change-detail",
+      "-host",
+      "https://default-review.googlesource.com",
+      "-input",
+      "{\"change_id\": \"2345\", \"params\": {\"o\": [\"CURRENT_COMMIT\", \"CURRENT_REVISION\"]}}",
+      "-output",
+      "/path/to/tmp/json"
+    ],
+    "name": "checkout pigweed.change data.process gerrit changes.resolve CL deps.details default:2345",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@4@@@",
+      "@@@STEP_LOG_LINE@json.output@{@@@",
+      "@@@STEP_LOG_LINE@json.output@  \"_number\": 2345, @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"current_revision\": \"HASH\", @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"project\": \"default_name\", @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"revisions\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@    \"HASH\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@      \"_number\": 1, @@@",
+      "@@@STEP_LOG_LINE@json.output@      \"commit\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@        \"message\": \"\"@@@",
+      "@@@STEP_LOG_LINE@json.output@      }@@@",
+      "@@@STEP_LOG_LINE@json.output@    }@@@",
+      "@@@STEP_LOG_LINE@json.output@  }, @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"status\": \"NEW\"@@@",
+      "@@@STEP_LOG_LINE@json.output@}@@@",
+      "@@@STEP_LOG_END@json.output@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "checkout pigweed.change data.process gerrit changes.resolve CL deps.resolve deps for default:2345",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@4@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "checkout pigweed.change data.process gerrit changes.resolve CL deps.pass",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@4@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "[CACHE]/cipd/infra/tools/luci/gerrit/latest/gerrit",
+      "change-detail",
+      "-host",
+      "https://default-review.googlesource.com",
+      "-input",
+      "{\"change_id\": \"2345\"}",
+      "-output",
+      "/path/to/tmp/json"
+    ],
+    "name": "checkout pigweed.change data.process gerrit changes.details",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@3@@@",
+      "@@@STEP_LOG_LINE@json.output@{@@@",
+      "@@@STEP_LOG_LINE@json.output@  \"branch\": \"master\"@@@",
+      "@@@STEP_LOG_LINE@json.output@}@@@",
+      "@@@STEP_LOG_END@json.output@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "checkout pigweed.change data.changes",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "checkout pigweed.change data.changes.pigweed:1234",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@3@@@",
+      "@@@STEP_SUMMARY_TEXT@_Change(number=1234L, remote='https://pigweed.googlesource.com/pigweed_name', ref='refs/changes/34/1234/1', rebase=True, branch='master', gerrit_name=u'pigweed')@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "checkout pigweed.change data.changes.default:2345",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@3@@@",
+      "@@@STEP_SUMMARY_TEXT@_Change(number=2345, remote='https://default.googlesource.com/default_name', ref='refs/changes/45/2345/1', rebase=True, branch='master', gerrit_name='default')@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "ensure-directory",
+      "--mode",
+      "0777",
+      "[START_DIR]/checkout"
+    ],
+    "infra_step": true,
+    "name": "checkout pigweed.mkdir checkout",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "ensure-directory",
+      "--mode",
+      "0777",
+      "[START_DIR]/depot_tools"
+    ],
+    "cwd": "[START_DIR]/checkout",
+    "infra_step": true,
+    "name": "checkout pigweed.makedirs",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "init"
+    ],
+    "cwd": "[START_DIR]/depot_tools",
+    "infra_step": true,
+    "name": "checkout pigweed.git init",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "remote",
+      "add",
+      "origin",
+      "https://chromium.googlesource.com/chromium/tools/depot_tools.git"
+    ],
+    "cwd": "[START_DIR]/depot_tools",
+    "infra_step": true,
+    "name": "checkout pigweed.git remote",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "config",
+      "fetch.uriprotocols",
+      "https"
+    ],
+    "cwd": "[START_DIR]/depot_tools",
+    "infra_step": true,
+    "name": "checkout pigweed.set fetch.uriprotocols",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "checkout pigweed.cache",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "ensure-directory",
+      "--mode",
+      "0777",
+      "[CACHE]/git/chromium.googlesource.com-chromium-tools-depot_tools.git"
+    ],
+    "cwd": "[START_DIR]/depot_tools",
+    "infra_step": true,
+    "name": "checkout pigweed.cache.makedirs",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "init",
+      "--bare"
+    ],
+    "cwd": "[CACHE]/git/chromium.googlesource.com-chromium-tools-depot_tools.git",
+    "infra_step": true,
+    "name": "checkout pigweed.cache.git init",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "config",
+      "remote.origin.url",
+      "https://chromium.googlesource.com/chromium/tools/depot_tools.git"
+    ],
+    "cwd": "[CACHE]/git/chromium.googlesource.com-chromium-tools-depot_tools.git",
+    "infra_step": true,
+    "name": "checkout pigweed.cache.remote set-url",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "config",
+      "fetch.uriprotocols",
+      "https"
+    ],
+    "cwd": "[CACHE]/git/chromium.googlesource.com-chromium-tools-depot_tools.git",
+    "infra_step": true,
+    "name": "checkout pigweed.cache.set fetch.uriprotocols",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "config",
+      "--replace-all",
+      "remote.origin.fetch",
+      "+refs/heads/*:refs/heads/*",
+      "\\+refs/heads/\\*:.*"
+    ],
+    "cwd": "[CACHE]/git/chromium.googlesource.com-chromium-tools-depot_tools.git",
+    "infra_step": true,
+    "name": "checkout pigweed.cache.replace fetch configs",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "fetch",
+      "--prune",
+      "--tags",
+      "origin"
+    ],
+    "cwd": "[CACHE]/git/chromium.googlesource.com-chromium-tools-depot_tools.git",
+    "infra_step": true,
+    "name": "checkout pigweed.cache.git fetch",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "ensure-directory",
+      "--mode",
+      "0777",
+      "[START_DIR]/depot_tools/.git/objects/info"
+    ],
+    "cwd": "[START_DIR]/depot_tools",
+    "infra_step": true,
+    "name": "checkout pigweed.cache.makedirs object/info",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "[CACHE]/git/chromium.googlesource.com-chromium-tools-depot_tools.git/objects\n",
+      "[START_DIR]/depot_tools/.git/objects/info/alternates"
+    ],
+    "cwd": "[START_DIR]/depot_tools",
+    "infra_step": true,
+    "name": "checkout pigweed.cache.alternates",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@",
+      "@@@STEP_LOG_LINE@alternates@[CACHE]/git/chromium.googlesource.com-chromium-tools-depot_tools.git/objects@@@",
+      "@@@STEP_LOG_END@alternates@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "fetch",
+      "--tags",
+      "origin",
+      "master"
+    ],
+    "cwd": "[START_DIR]/depot_tools",
+    "infra_step": true,
+    "name": "checkout pigweed.git fetch",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "checkout",
+      "-f",
+      "FETCH_HEAD"
+    ],
+    "cwd": "[START_DIR]/depot_tools",
+    "infra_step": true,
+    "name": "checkout pigweed.git checkout",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "rev-parse",
+      "HEAD"
+    ],
+    "cwd": "[START_DIR]/depot_tools",
+    "infra_step": true,
+    "name": "checkout pigweed.git rev-parse",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "clean",
+      "-f",
+      "-d",
+      "-x"
+    ],
+    "cwd": "[START_DIR]/depot_tools",
+    "infra_step": true,
+    "name": "checkout pigweed.git clean",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "checkout pigweed.submodule",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "submodule",
+      "sync"
+    ],
+    "cwd": "[START_DIR]/depot_tools",
+    "infra_step": true,
+    "name": "checkout pigweed.submodule.git submodule sync",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "submodule",
+      "update",
+      "--init"
+    ],
+    "cwd": "[START_DIR]/depot_tools",
+    "infra_step": true,
+    "name": "checkout pigweed.submodule.git submodule update",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "repo",
+      "init",
+      "--manifest-url",
+      "https://pigweed.googlesource.com/pigweed/manifest",
+      "--groups",
+      "all",
+      "--manifest-branch",
+      "master"
+    ],
+    "cwd": "[START_DIR]/checkout",
+    "env_prefixes": {
+      "PATH": [
+        "[START_DIR]/depot_tools"
+      ]
+    },
+    "infra_step": true,
+    "name": "checkout pigweed.repo init",
+    "timeout": 20,
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "find",
+      ".repo/",
+      "-type",
+      "f",
+      "-name",
+      "*.lock",
+      "-print",
+      "-delete"
+    ],
+    "cwd": "[START_DIR]/checkout",
+    "infra_step": true,
+    "name": "checkout pigweed.clear repo locks",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "repo",
+      "forall",
+      "--ignore-missing",
+      "-j",
+      "32",
+      "-c",
+      "find",
+      ".git/",
+      "-type",
+      "f",
+      "-name",
+      "*.lock",
+      "-print",
+      "-delete"
+    ],
+    "cwd": "[START_DIR]/checkout",
+    "env_prefixes": {
+      "PATH": [
+        "[START_DIR]/depot_tools"
+      ]
+    },
+    "infra_step": true,
+    "name": "checkout pigweed.clear git locks",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "checkout pigweed.read manifest",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@",
+      "@@@STEP_LOG_LINE@raw@<?xml version=\"1.0\" encoding=\"UTF-8\"?>@@@",
+      "@@@STEP_LOG_LINE@raw@<manifest>@@@",
+      "@@@STEP_LOG_LINE@raw@  <remote@@@",
+      "@@@STEP_LOG_LINE@raw@    name=\"default_remote\"@@@",
+      "@@@STEP_LOG_LINE@raw@    fetch=\"sso://default\"@@@",
+      "@@@STEP_LOG_LINE@raw@    />@@@",
+      "@@@STEP_LOG_LINE@raw@@@@",
+      "@@@STEP_LOG_LINE@raw@  <remote@@@",
+      "@@@STEP_LOG_LINE@raw@    name=\"pigweed_remote\"@@@",
+      "@@@STEP_LOG_LINE@raw@    revision=\"master\"@@@",
+      "@@@STEP_LOG_LINE@raw@    fetch=\"https://pigweed.googlesource.com\"@@@",
+      "@@@STEP_LOG_LINE@raw@    review=\"https://pigweed-review.googlesource.com\"@@@",
+      "@@@STEP_LOG_LINE@raw@    />@@@",
+      "@@@STEP_LOG_LINE@raw@@@@",
+      "@@@STEP_LOG_LINE@raw@  <remote@@@",
+      "@@@STEP_LOG_LINE@raw@    name=\"pigweed-internal_remote\"@@@",
+      "@@@STEP_LOG_LINE@raw@    revision=\"master\"@@@",
+      "@@@STEP_LOG_LINE@raw@    fetch=\"https://pigweed-internal.googlesource.com\"@@@",
+      "@@@STEP_LOG_LINE@raw@    review=\"https://pigweed-internal-review.googlesource.com\"@@@",
+      "@@@STEP_LOG_LINE@raw@    />@@@",
+      "@@@STEP_LOG_LINE@raw@@@@",
+      "@@@STEP_LOG_LINE@raw@  <default@@@",
+      "@@@STEP_LOG_LINE@raw@    remote=\"default_remote\"@@@",
+      "@@@STEP_LOG_LINE@raw@    revision=\"master\"@@@",
+      "@@@STEP_LOG_LINE@raw@    />@@@",
+      "@@@STEP_LOG_LINE@raw@@@@",
+      "@@@STEP_LOG_LINE@raw@  <project@@@",
+      "@@@STEP_LOG_LINE@raw@    name=\"default_name\"@@@",
+      "@@@STEP_LOG_LINE@raw@    path=\"default_path\"@@@",
+      "@@@STEP_LOG_LINE@raw@    />@@@",
+      "@@@STEP_LOG_LINE@raw@@@@",
+      "@@@STEP_LOG_LINE@raw@  <project@@@",
+      "@@@STEP_LOG_LINE@raw@    name=\"pigweed_name\"@@@",
+      "@@@STEP_LOG_LINE@raw@    path=\"pigweed_path\"@@@",
+      "@@@STEP_LOG_LINE@raw@    remote=\"pigweed_remote\"@@@",
+      "@@@STEP_LOG_LINE@raw@    revision=\"master\"@@@",
+      "@@@STEP_LOG_LINE@raw@    />@@@",
+      "@@@STEP_LOG_LINE@raw@@@@",
+      "@@@STEP_LOG_LINE@raw@  <project@@@",
+      "@@@STEP_LOG_LINE@raw@    name=\"pigweed-internal_name\"@@@",
+      "@@@STEP_LOG_LINE@raw@    path=\"pigweed-internal_path\"@@@",
+      "@@@STEP_LOG_LINE@raw@    remote=\"pigweed-internal_remote\"@@@",
+      "@@@STEP_LOG_LINE@raw@    />@@@",
+      "@@@STEP_LOG_LINE@raw@@@@",
+      "@@@STEP_LOG_LINE@raw@  <project@@@",
+      "@@@STEP_LOG_LINE@raw@    name=\"pinned\"@@@",
+      "@@@STEP_LOG_LINE@raw@    path=\"pinned\"@@@",
+      "@@@STEP_LOG_LINE@raw@    remote=\"pigweed_remote\"@@@",
+      "@@@STEP_LOG_LINE@raw@    revision=\"0123456789012345678901234567890123456789\"@@@",
+      "@@@STEP_LOG_LINE@raw@    upstream=\"master\"@@@",
+      "@@@STEP_LOG_LINE@raw@    />@@@",
+      "@@@STEP_LOG_LINE@raw@</manifest>@@@",
+      "@@@STEP_LOG_END@raw@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "[START_DIR]/checkout/.repo/manifests/default.xml",
+      "/path/to/tmp/"
+    ],
+    "cwd": "[START_DIR]/checkout",
+    "infra_step": true,
+    "name": "checkout pigweed.read manifest.read file",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@",
+      "@@@STEP_LOG_LINE@default.xml@<?xml version=\"1.0\" encoding=\"UTF-8\"?>@@@",
+      "@@@STEP_LOG_LINE@default.xml@<manifest>@@@",
+      "@@@STEP_LOG_LINE@default.xml@  <remote@@@",
+      "@@@STEP_LOG_LINE@default.xml@    name=\"default_remote\"@@@",
+      "@@@STEP_LOG_LINE@default.xml@    fetch=\"sso://default\"@@@",
+      "@@@STEP_LOG_LINE@default.xml@    />@@@",
+      "@@@STEP_LOG_LINE@default.xml@@@@",
+      "@@@STEP_LOG_LINE@default.xml@  <remote@@@",
+      "@@@STEP_LOG_LINE@default.xml@    name=\"pigweed_remote\"@@@",
+      "@@@STEP_LOG_LINE@default.xml@    revision=\"master\"@@@",
+      "@@@STEP_LOG_LINE@default.xml@    fetch=\"https://pigweed.googlesource.com\"@@@",
+      "@@@STEP_LOG_LINE@default.xml@    review=\"https://pigweed-review.googlesource.com\"@@@",
+      "@@@STEP_LOG_LINE@default.xml@    />@@@",
+      "@@@STEP_LOG_LINE@default.xml@@@@",
+      "@@@STEP_LOG_LINE@default.xml@  <remote@@@",
+      "@@@STEP_LOG_LINE@default.xml@    name=\"pigweed-internal_remote\"@@@",
+      "@@@STEP_LOG_LINE@default.xml@    revision=\"master\"@@@",
+      "@@@STEP_LOG_LINE@default.xml@    fetch=\"https://pigweed-internal.googlesource.com\"@@@",
+      "@@@STEP_LOG_LINE@default.xml@    review=\"https://pigweed-internal-review.googlesource.com\"@@@",
+      "@@@STEP_LOG_LINE@default.xml@    />@@@",
+      "@@@STEP_LOG_LINE@default.xml@@@@",
+      "@@@STEP_LOG_LINE@default.xml@  <default@@@",
+      "@@@STEP_LOG_LINE@default.xml@    remote=\"default_remote\"@@@",
+      "@@@STEP_LOG_LINE@default.xml@    revision=\"master\"@@@",
+      "@@@STEP_LOG_LINE@default.xml@    />@@@",
+      "@@@STEP_LOG_LINE@default.xml@@@@",
+      "@@@STEP_LOG_LINE@default.xml@  <project@@@",
+      "@@@STEP_LOG_LINE@default.xml@    name=\"default_name\"@@@",
+      "@@@STEP_LOG_LINE@default.xml@    path=\"default_path\"@@@",
+      "@@@STEP_LOG_LINE@default.xml@    />@@@",
+      "@@@STEP_LOG_LINE@default.xml@@@@",
+      "@@@STEP_LOG_LINE@default.xml@  <project@@@",
+      "@@@STEP_LOG_LINE@default.xml@    name=\"pigweed_name\"@@@",
+      "@@@STEP_LOG_LINE@default.xml@    path=\"pigweed_path\"@@@",
+      "@@@STEP_LOG_LINE@default.xml@    remote=\"pigweed_remote\"@@@",
+      "@@@STEP_LOG_LINE@default.xml@    revision=\"master\"@@@",
+      "@@@STEP_LOG_LINE@default.xml@    />@@@",
+      "@@@STEP_LOG_LINE@default.xml@@@@",
+      "@@@STEP_LOG_LINE@default.xml@  <project@@@",
+      "@@@STEP_LOG_LINE@default.xml@    name=\"pigweed-internal_name\"@@@",
+      "@@@STEP_LOG_LINE@default.xml@    path=\"pigweed-internal_path\"@@@",
+      "@@@STEP_LOG_LINE@default.xml@    remote=\"pigweed-internal_remote\"@@@",
+      "@@@STEP_LOG_LINE@default.xml@    />@@@",
+      "@@@STEP_LOG_LINE@default.xml@@@@",
+      "@@@STEP_LOG_LINE@default.xml@  <project@@@",
+      "@@@STEP_LOG_LINE@default.xml@    name=\"pinned\"@@@",
+      "@@@STEP_LOG_LINE@default.xml@    path=\"pinned\"@@@",
+      "@@@STEP_LOG_LINE@default.xml@    remote=\"pigweed_remote\"@@@",
+      "@@@STEP_LOG_LINE@default.xml@    revision=\"0123456789012345678901234567890123456789\"@@@",
+      "@@@STEP_LOG_LINE@default.xml@    upstream=\"master\"@@@",
+      "@@@STEP_LOG_LINE@default.xml@    />@@@",
+      "@@@STEP_LOG_LINE@default.xml@</manifest>@@@",
+      "@@@STEP_LOG_END@default.xml@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "{\"projects\": [{\"name\": \"default_name\", \"path\": \"default_path\", \"remote\": \"default_remote\", \"revision\": \"master\", \"upstream\": \"master\", \"url\": \"https://default.googlesource.com/default_name\"}, {\"name\": \"pigweed_name\", \"path\": \"pigweed_path\", \"remote\": \"pigweed_remote\", \"revision\": \"master\", \"upstream\": \"master\", \"url\": \"https://pigweed.googlesource.com/pigweed_name\"}, {\"name\": \"pigweed-internal_name\", \"path\": \"pigweed-internal_path\", \"remote\": \"pigweed-internal_remote\", \"revision\": \"master\", \"upstream\": \"master\", \"url\": \"https://pigweed-internal.googlesource.com/pigweed-internal_name\"}, {\"name\": \"pinned\", \"path\": \"pinned\", \"remote\": \"pigweed_remote\", \"revision\": \"0123456789012345678901234567890123456789\", \"upstream\": \"master\", \"url\": \"https://pigweed.googlesource.com/pinned\"}], \"remotes\": {\"default_remote\": {\"fetch\": {\"https\": \"https://default.googlesource.com\", \"url\": \"sso://default\"}, \"name\": \"default_remote\", \"review\": null, \"revision\": null}, \"pigweed-internal_remote\": {\"fetch\": {\"https\": \"https://pigweed-internal.googlesource.com\", \"url\": \"https://pigweed-internal.googlesource.com\"}, \"name\": \"pigweed-internal_remote\", \"review\": \"https://pigweed-internal-review.googlesource.com\", \"revision\": \"master\"}, \"pigweed_remote\": {\"fetch\": {\"https\": \"https://pigweed.googlesource.com\", \"url\": \"https://pigweed.googlesource.com\"}, \"name\": \"pigweed_remote\", \"review\": \"https://pigweed-review.googlesource.com\", \"revision\": \"master\"}}}",
+      "[START_DIR]/manifest.json"
+    ],
+    "cwd": "[START_DIR]/checkout",
+    "infra_step": true,
+    "name": "checkout pigweed.read manifest.manifest json",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@",
+      "@@@STEP_LOG_LINE@manifest.json@{\"projects\": [{\"name\": \"default_name\", \"path\": \"default_path\", \"remote\": \"default_remote\", \"revision\": \"master\", \"upstream\": \"master\", \"url\": \"https://default.googlesource.com/default_name\"}, {\"name\": \"pigweed_name\", \"path\": \"pigweed_path\", \"remote\": \"pigweed_remote\", \"revision\": \"master\", \"upstream\": \"master\", \"url\": \"https://pigweed.googlesource.com/pigweed_name\"}, {\"name\": \"pigweed-internal_name\", \"path\": \"pigweed-internal_path\", \"remote\": \"pigweed-internal_remote\", \"revision\": \"master\", \"upstream\": \"master\", \"url\": \"https://pigweed-internal.googlesource.com/pigweed-internal_name\"}, {\"name\": \"pinned\", \"path\": \"pinned\", \"remote\": \"pigweed_remote\", \"revision\": \"0123456789012345678901234567890123456789\", \"upstream\": \"master\", \"url\": \"https://pigweed.googlesource.com/pinned\"}], \"remotes\": {\"default_remote\": {\"fetch\": {\"https\": \"https://default.googlesource.com\", \"url\": \"sso://default\"}, \"name\": \"default_remote\", \"review\": null, \"revision\": null}, \"pigweed-internal_remote\": {\"fetch\": {\"https\": \"https://pigweed-internal.googlesource.com\", \"url\": \"https://pigweed-internal.googlesource.com\"}, \"name\": \"pigweed-internal_remote\", \"review\": \"https://pigweed-internal-review.googlesource.com\", \"revision\": \"master\"}, \"pigweed_remote\": {\"fetch\": {\"https\": \"https://pigweed.googlesource.com\", \"url\": \"https://pigweed.googlesource.com\"}, \"name\": \"pigweed_remote\", \"review\": \"https://pigweed-review.googlesource.com\", \"revision\": \"master\"}}}@@@",
+      "@@@STEP_LOG_END@manifest.json@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "config",
+      "--global",
+      "--add",
+      "url.https://default.googlesource.com/a.insteadof",
+      "sso://default"
+    ],
+    "cwd": "[START_DIR]/checkout",
+    "name": "checkout pigweed.git insteadof sso://default",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "repo",
+      "sync",
+      "--force-sync",
+      "--current-branch",
+      "--jobs",
+      "20"
+    ],
+    "cwd": "[START_DIR]/checkout",
+    "env_prefixes": {
+      "PATH": [
+        "[START_DIR]/depot_tools"
+      ]
+    },
+    "infra_step": true,
+    "name": "checkout pigweed.repo sync",
+    "timeout": 120,
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "repo",
+      "start",
+      "base",
+      "--all"
+    ],
+    "cwd": "[START_DIR]/checkout",
+    "env_prefixes": {
+      "PATH": [
+        "[START_DIR]/depot_tools"
+      ]
+    },
+    "infra_step": true,
+    "name": "checkout pigweed.repo start",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "checkout pigweed.apply pigweed:1234",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@",
+      "@@@STEP_LINK@gerrit@https://pigweed-review.googlesource.com/c/1234@@@",
+      "@@@STEP_LINK@gitiles@https://pigweed.googlesource.com/pigweed_name/+/refs/changes/34/1234/1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "fetch",
+      "https://pigweed.googlesource.com/pigweed_name",
+      "refs/changes/34/1234/1"
+    ],
+    "cwd": "[START_DIR]/checkout/pigweed_path",
+    "infra_step": true,
+    "name": "checkout pigweed.apply pigweed:1234.git fetch",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "checkout",
+      "--recurse-submodules",
+      "-b",
+      "working",
+      "FETCH_HEAD"
+    ],
+    "cwd": "[START_DIR]/checkout/pigweed_path",
+    "infra_step": true,
+    "name": "checkout pigweed.apply pigweed:1234.git checkout patch",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "log",
+      "--oneline",
+      "-n",
+      "10"
+    ],
+    "cwd": "[START_DIR]/checkout/pigweed_path",
+    "infra_step": true,
+    "name": "checkout pigweed.apply pigweed:1234.pre-rebase log",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "fetch",
+      "pigweed_remote",
+      "master"
+    ],
+    "cwd": "[START_DIR]/checkout/pigweed_path",
+    "infra_step": true,
+    "name": "checkout pigweed.apply pigweed:1234.git fetch (2)",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "rebase",
+      "pigweed_remote/master"
+    ],
+    "cwd": "[START_DIR]/checkout/pigweed_path",
+    "name": "checkout pigweed.apply pigweed:1234.git rebase",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "submodule",
+      "update"
+    ],
+    "cwd": "[START_DIR]/checkout/pigweed_path",
+    "name": "checkout pigweed.apply pigweed:1234.git submodule",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "checkout pigweed.apply pigweed:1234.compare branch name",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@",
+      "@@@STEP_SUMMARY_TEXT@CL branch: master\nupstream branch: master@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "checkout pigweed.apply default:2345",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@",
+      "@@@STEP_LINK@gerrit@https://default-review.googlesource.com/c/2345@@@",
+      "@@@STEP_LINK@gitiles@https://default.googlesource.com/default_name/+/refs/changes/45/2345/1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "fetch",
+      "https://default.googlesource.com/default_name",
+      "refs/changes/45/2345/1"
+    ],
+    "cwd": "[START_DIR]/checkout/default_path",
+    "infra_step": true,
+    "name": "checkout pigweed.apply default:2345.git fetch",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "checkout",
+      "--recurse-submodules",
+      "-b",
+      "working",
+      "FETCH_HEAD"
+    ],
+    "cwd": "[START_DIR]/checkout/default_path",
+    "infra_step": true,
+    "name": "checkout pigweed.apply default:2345.git checkout patch",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "log",
+      "--oneline",
+      "-n",
+      "10"
+    ],
+    "cwd": "[START_DIR]/checkout/default_path",
+    "infra_step": true,
+    "name": "checkout pigweed.apply default:2345.pre-rebase log",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "fetch",
+      "default_remote",
+      "master"
+    ],
+    "cwd": "[START_DIR]/checkout/default_path",
+    "infra_step": true,
+    "name": "checkout pigweed.apply default:2345.git fetch (2)",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "rebase",
+      "default_remote/master"
+    ],
+    "cwd": "[START_DIR]/checkout/default_path",
+    "name": "checkout pigweed.apply default:2345.git rebase",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "submodule",
+      "update"
+    ],
+    "cwd": "[START_DIR]/checkout/default_path",
+    "name": "checkout pigweed.apply default:2345.git submodule",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "checkout pigweed.apply default:2345.compare branch name",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@",
+      "@@@STEP_SUMMARY_TEXT@CL branch: master\nupstream branch: master@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "repo",
+      "manifest",
+      "-r"
+    ],
+    "cwd": "[START_DIR]/checkout",
+    "env_prefixes": {
+      "PATH": [
+        "[START_DIR]/depot_tools"
+      ]
+    },
+    "infra_step": true,
+    "name": "checkout pigweed.repo manifest",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@",
+      "@@@STEP_LOG_LINE@raw_io.output@<manifest></manifest>@@@",
+      "@@@STEP_LOG_END@raw_io.output@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "checkout pigweed.root",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@",
+      "@@@STEP_SUMMARY_TEXT@root=[START_DIR]/checkout\nself._root=[START_DIR]/checkout\n@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "listdir",
+      "[START_DIR]/checkout"
+    ],
+    "infra_step": true,
+    "name": "checkout pigweed.ls",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@",
+      "@@@STEP_LOG_LINE@listdir@[START_DIR]/checkout/.repo@@@",
+      "@@@STEP_LOG_LINE@listdir@[START_DIR]/checkout/foo@@@",
+      "@@@STEP_LOG_END@listdir@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "root",
+    "~followup_annotations": [
+      "@@@STEP_SUMMARY_TEXT@[START_DIR]/checkout/foo@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "repo_top",
+    "~followup_annotations": [
+      "@@@STEP_SUMMARY_TEXT@[START_DIR]/checkout@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "use_repo",
+    "~followup_annotations": [
+      "@@@STEP_SUMMARY_TEXT@True@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "changes"
+  },
+  {
+    "cmd": [],
+    "name": "changes.0",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@",
+      "@@@STEP_SUMMARY_TEXT@_Change(number=1234L, remote='https://pigweed.googlesource.com/pigweed_name', ref='refs/changes/34/1234/1', rebase=True, branch='master', gerrit_name=u'pigweed')@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "changes.1",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@",
+      "@@@STEP_SUMMARY_TEXT@_Change(number=2345, remote='https://default.googlesource.com/default_name', ref='refs/changes/45/2345/1', rebase=True, branch='master', gerrit_name='default')@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "manifest_snapshot",
+    "~followup_annotations": [
+      "@@@STEP_SUMMARY_TEXT@<manifest></manifest>@@@"
+    ]
+  },
+  {
+    "name": "$result"
+  }
+]
\ No newline at end of file
diff --git a/recipe_modules/checkout/tests/repo.expected/try-multiple-onenotapplied.json b/recipe_modules/checkout/tests/repo.expected/try-multiple-onenotapplied.json
index ad4db87..b139c09 100644
--- a/recipe_modules/checkout/tests/repo.expected/try-multiple-onenotapplied.json
+++ b/recipe_modules/checkout/tests/repo.expected/try-multiple-onenotapplied.json
@@ -3,6 +3,8 @@
     "cmd": [],
     "name": "checkout pigweed",
     "~followup_annotations": [
+      "@@@STEP_SUMMARY_TEXT@Failed to apply the following CLs@@@",
+      "@@@STEP_LINK@pigweed:3456@https://pigweed-review.googlesource.com/c/3456@@@",
       "@@@STEP_WARNINGS@@@"
     ]
   },
@@ -158,6 +160,57 @@
   },
   {
     "cmd": [],
+    "name": "checkout pigweed.change data.process gerrit changes.resolve CL deps",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@3@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "[CACHE]/cipd/infra/tools/luci/gerrit/latest/gerrit",
+      "change-detail",
+      "-host",
+      "https://pigweed-review.googlesource.com",
+      "-input",
+      "{\"change_id\": \"3456\", \"params\": {\"o\": [\"CURRENT_COMMIT\", \"CURRENT_REVISION\"]}}",
+      "-output",
+      "/path/to/tmp/json"
+    ],
+    "name": "checkout pigweed.change data.process gerrit changes.resolve CL deps.details pigweed:3456",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@4@@@",
+      "@@@STEP_LOG_LINE@json.output@{@@@",
+      "@@@STEP_LOG_LINE@json.output@  \"current_revision\": \"HASH\", @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"project\": \"project\", @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"revisions\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@    \"HASH\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@      \"_number\": 1, @@@",
+      "@@@STEP_LOG_LINE@json.output@      \"commit\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@        \"message\": \"\"@@@",
+      "@@@STEP_LOG_LINE@json.output@      }@@@",
+      "@@@STEP_LOG_LINE@json.output@    }@@@",
+      "@@@STEP_LOG_LINE@json.output@  }, @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"status\": \"NEW\"@@@",
+      "@@@STEP_LOG_LINE@json.output@}@@@",
+      "@@@STEP_LOG_END@json.output@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "checkout pigweed.change data.process gerrit changes.resolve CL deps.resolve deps for pigweed:3456",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@4@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "checkout pigweed.change data.process gerrit changes.resolve CL deps.pass",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@4@@@"
+    ]
+  },
+  {
+    "cmd": [],
     "name": "checkout pigweed.change data.changes",
     "~followup_annotations": [
       "@@@STEP_NEST_LEVEL@2@@@"
diff --git a/recipe_modules/checkout/tests/repo.expected/try-multiple.json b/recipe_modules/checkout/tests/repo.expected/try-multiple.json
index 92867f0..d03a0f2 100644
--- a/recipe_modules/checkout/tests/repo.expected/try-multiple.json
+++ b/recipe_modules/checkout/tests/repo.expected/try-multiple.json
@@ -128,6 +128,57 @@
   },
   {
     "cmd": [],
+    "name": "checkout pigweed.change data.process gerrit changes.resolve CL deps",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@3@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "[CACHE]/cipd/infra/tools/luci/gerrit/latest/gerrit",
+      "change-detail",
+      "-host",
+      "https://default-review.googlesource.com",
+      "-input",
+      "{\"change_id\": \"2345\", \"params\": {\"o\": [\"CURRENT_COMMIT\", \"CURRENT_REVISION\"]}}",
+      "-output",
+      "/path/to/tmp/json"
+    ],
+    "name": "checkout pigweed.change data.process gerrit changes.resolve CL deps.details default:2345",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@4@@@",
+      "@@@STEP_LOG_LINE@json.output@{@@@",
+      "@@@STEP_LOG_LINE@json.output@  \"current_revision\": \"HASH\", @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"project\": \"project\", @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"revisions\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@    \"HASH\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@      \"_number\": 1, @@@",
+      "@@@STEP_LOG_LINE@json.output@      \"commit\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@        \"message\": \"\"@@@",
+      "@@@STEP_LOG_LINE@json.output@      }@@@",
+      "@@@STEP_LOG_LINE@json.output@    }@@@",
+      "@@@STEP_LOG_LINE@json.output@  }, @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"status\": \"NEW\"@@@",
+      "@@@STEP_LOG_LINE@json.output@}@@@",
+      "@@@STEP_LOG_END@json.output@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "checkout pigweed.change data.process gerrit changes.resolve CL deps.resolve deps for default:2345",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@4@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "checkout pigweed.change data.process gerrit changes.resolve CL deps.pass",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@4@@@"
+    ]
+  },
+  {
+    "cmd": [],
     "name": "checkout pigweed.change data.changes",
     "~followup_annotations": [
       "@@@STEP_NEST_LEVEL@2@@@"
diff --git a/recipe_modules/checkout/tests/repo.expected/try.json b/recipe_modules/checkout/tests/repo.expected/try.json
index 0ea63a3..a30a200 100644
--- a/recipe_modules/checkout/tests/repo.expected/try.json
+++ b/recipe_modules/checkout/tests/repo.expected/try.json
@@ -101,6 +101,57 @@
   },
   {
     "cmd": [],
+    "name": "checkout pigweed.change data.process gerrit changes.resolve CL deps",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@3@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "[CACHE]/cipd/infra/tools/luci/gerrit/latest/gerrit",
+      "change-detail",
+      "-host",
+      "https://pigweed-review.googlesource.com",
+      "-input",
+      "{\"change_id\": \"123456\", \"params\": {\"o\": [\"CURRENT_COMMIT\", \"CURRENT_REVISION\"]}}",
+      "-output",
+      "/path/to/tmp/json"
+    ],
+    "name": "checkout pigweed.change data.process gerrit changes.resolve CL deps.details pigweed:123456",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@4@@@",
+      "@@@STEP_LOG_LINE@json.output@{@@@",
+      "@@@STEP_LOG_LINE@json.output@  \"current_revision\": \"HASH\", @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"project\": \"project\", @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"revisions\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@    \"HASH\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@      \"_number\": 1, @@@",
+      "@@@STEP_LOG_LINE@json.output@      \"commit\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@        \"message\": \"\"@@@",
+      "@@@STEP_LOG_LINE@json.output@      }@@@",
+      "@@@STEP_LOG_LINE@json.output@    }@@@",
+      "@@@STEP_LOG_LINE@json.output@  }, @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"status\": \"NEW\"@@@",
+      "@@@STEP_LOG_LINE@json.output@}@@@",
+      "@@@STEP_LOG_END@json.output@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "checkout pigweed.change data.process gerrit changes.resolve CL deps.resolve deps for pigweed:123456",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@4@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "checkout pigweed.change data.process gerrit changes.resolve CL deps.pass",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@4@@@"
+    ]
+  },
+  {
+    "cmd": [],
     "name": "checkout pigweed.change data.changes",
     "~followup_annotations": [
       "@@@STEP_NEST_LEVEL@2@@@"
diff --git a/recipe_modules/checkout/tests/repo.expected/try_manifest.json b/recipe_modules/checkout/tests/repo.expected/try_manifest.json
index 26674a1..533f3f2 100644
--- a/recipe_modules/checkout/tests/repo.expected/try_manifest.json
+++ b/recipe_modules/checkout/tests/repo.expected/try_manifest.json
@@ -101,6 +101,57 @@
   },
   {
     "cmd": [],
+    "name": "checkout pigweed.change data.process gerrit changes.resolve CL deps",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@3@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "[CACHE]/cipd/infra/tools/luci/gerrit/latest/gerrit",
+      "change-detail",
+      "-host",
+      "https://pigweed-review.googlesource.com",
+      "-input",
+      "{\"change_id\": \"123456\", \"params\": {\"o\": [\"CURRENT_COMMIT\", \"CURRENT_REVISION\"]}}",
+      "-output",
+      "/path/to/tmp/json"
+    ],
+    "name": "checkout pigweed.change data.process gerrit changes.resolve CL deps.details pigweed:123456",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@4@@@",
+      "@@@STEP_LOG_LINE@json.output@{@@@",
+      "@@@STEP_LOG_LINE@json.output@  \"current_revision\": \"HASH\", @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"project\": \"project\", @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"revisions\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@    \"HASH\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@      \"_number\": 1, @@@",
+      "@@@STEP_LOG_LINE@json.output@      \"commit\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@        \"message\": \"\"@@@",
+      "@@@STEP_LOG_LINE@json.output@      }@@@",
+      "@@@STEP_LOG_LINE@json.output@    }@@@",
+      "@@@STEP_LOG_LINE@json.output@  }, @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"status\": \"NEW\"@@@",
+      "@@@STEP_LOG_LINE@json.output@}@@@",
+      "@@@STEP_LOG_END@json.output@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "checkout pigweed.change data.process gerrit changes.resolve CL deps.resolve deps for pigweed:123456",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@4@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "checkout pigweed.change data.process gerrit changes.resolve CL deps.pass",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@4@@@"
+    ]
+  },
+  {
+    "cmd": [],
     "name": "checkout pigweed.change data.changes",
     "~followup_annotations": [
       "@@@STEP_NEST_LEVEL@2@@@"
diff --git a/recipe_modules/checkout/tests/repo.expected/try_repo_not_in_manifest.json b/recipe_modules/checkout/tests/repo.expected/try_repo_not_in_manifest.json
index 5977678..f843dbf 100644
--- a/recipe_modules/checkout/tests/repo.expected/try_repo_not_in_manifest.json
+++ b/recipe_modules/checkout/tests/repo.expected/try_repo_not_in_manifest.json
@@ -104,6 +104,57 @@
   },
   {
     "cmd": [],
+    "name": "checkout pigweed.change data.process gerrit changes.resolve CL deps",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@3@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "[CACHE]/cipd/infra/tools/luci/gerrit/latest/gerrit",
+      "change-detail",
+      "-host",
+      "https://foo-review.googlesource.com",
+      "-input",
+      "{\"change_id\": \"123456\", \"params\": {\"o\": [\"CURRENT_COMMIT\", \"CURRENT_REVISION\"]}}",
+      "-output",
+      "/path/to/tmp/json"
+    ],
+    "name": "checkout pigweed.change data.process gerrit changes.resolve CL deps.details foo:123456",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@4@@@",
+      "@@@STEP_LOG_LINE@json.output@{@@@",
+      "@@@STEP_LOG_LINE@json.output@  \"current_revision\": \"HASH\", @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"project\": \"project\", @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"revisions\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@    \"HASH\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@      \"_number\": 1, @@@",
+      "@@@STEP_LOG_LINE@json.output@      \"commit\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@        \"message\": \"\"@@@",
+      "@@@STEP_LOG_LINE@json.output@      }@@@",
+      "@@@STEP_LOG_LINE@json.output@    }@@@",
+      "@@@STEP_LOG_LINE@json.output@  }, @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"status\": \"NEW\"@@@",
+      "@@@STEP_LOG_LINE@json.output@}@@@",
+      "@@@STEP_LOG_END@json.output@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "checkout pigweed.change data.process gerrit changes.resolve CL deps.resolve deps for foo:123456",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@4@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "checkout pigweed.change data.process gerrit changes.resolve CL deps.pass",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@4@@@"
+    ]
+  },
+  {
+    "cmd": [],
     "name": "checkout pigweed.change data.changes",
     "~followup_annotations": [
       "@@@STEP_NEST_LEVEL@2@@@"
diff --git a/recipe_modules/checkout/tests/repo.py b/recipe_modules/checkout/tests/repo.py
index e9e6a6c..e2a6b17 100644
--- a/recipe_modules/checkout/tests/repo.py
+++ b/recipe_modules/checkout/tests/repo.py
@@ -16,6 +16,7 @@
 DEPS = [
     'fuchsia/status_check',
     'pigweed/checkout',
+    'pigweed/cq_deps',
     'recipe_engine/properties',
     'recipe_engine/step',
 ]
@@ -87,6 +88,31 @@
     )
 
     yield (
+        api.status_check.test('try-multiple-cqdeps')
+        + api.properties(**api.checkout.repo_properties())
+        + api.checkout.try_test_data(
+            git_repo='https://pigweed.googlesource.com/pigweed_name',
+            change_number=1234,
+            patch_set=1,
+        )
+        + api.cq_deps.details(
+            'pigweed:1234',
+            message='Cq-Depend: default:2345',
+            prefix='checkout pigweed.change data.process gerrit changes.',
+        )
+        + api.cq_deps.details(
+            'default:2345',
+            project='default_name',
+            prefix='checkout pigweed.change data.process gerrit changes.',
+        )
+        + api.checkout.manifest_test_data()
+        + api.checkout.root_files('foo', '.repo')
+        + api.checkout.all_changes_applied()
+        + api.checkout.change_applied('pigweed:1234')
+        + api.checkout.change_applied('default:2345')
+    )
+
+    yield (
         api.status_check.test('try-multiple-onenotapplied')
         + api.properties(**api.checkout.repo_properties())
         + api.checkout.try_test_data(
diff --git a/recipe_modules/checkout/tests/submodule.expected/submodule-try-gibberish.json b/recipe_modules/checkout/tests/submodule.expected/submodule-try-gibberish.json
index 8870d75..31a8c3f 100644
--- a/recipe_modules/checkout/tests/submodule.expected/submodule-try-gibberish.json
+++ b/recipe_modules/checkout/tests/submodule.expected/submodule-try-gibberish.json
@@ -104,6 +104,57 @@
   },
   {
     "cmd": [],
+    "name": "checkout pigweed.change data.process gerrit changes.resolve CL deps",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@3@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "[CACHE]/cipd/infra/tools/luci/gerrit/latest/gerrit",
+      "change-detail",
+      "-host",
+      "https://x-review.googlesource.com",
+      "-input",
+      "{\"change_id\": \"123456\", \"params\": {\"o\": [\"CURRENT_COMMIT\", \"CURRENT_REVISION\"]}}",
+      "-output",
+      "/path/to/tmp/json"
+    ],
+    "name": "checkout pigweed.change data.process gerrit changes.resolve CL deps.details x:123456",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@4@@@",
+      "@@@STEP_LOG_LINE@json.output@{@@@",
+      "@@@STEP_LOG_LINE@json.output@  \"current_revision\": \"HASH\", @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"project\": \"project\", @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"revisions\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@    \"HASH\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@      \"_number\": 1, @@@",
+      "@@@STEP_LOG_LINE@json.output@      \"commit\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@        \"message\": \"\"@@@",
+      "@@@STEP_LOG_LINE@json.output@      }@@@",
+      "@@@STEP_LOG_LINE@json.output@    }@@@",
+      "@@@STEP_LOG_LINE@json.output@  }, @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"status\": \"NEW\"@@@",
+      "@@@STEP_LOG_LINE@json.output@}@@@",
+      "@@@STEP_LOG_END@json.output@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "checkout pigweed.change data.process gerrit changes.resolve CL deps.resolve deps for x:123456",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@4@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "checkout pigweed.change data.process gerrit changes.resolve CL deps.pass",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@4@@@"
+    ]
+  },
+  {
+    "cmd": [],
     "name": "checkout pigweed.change data.changes",
     "~followup_annotations": [
       "@@@STEP_NEST_LEVEL@2@@@"
diff --git a/recipe_modules/checkout/tests/submodule.expected/submodule-try-multiple-cqdeps.json b/recipe_modules/checkout/tests/submodule.expected/submodule-try-multiple-cqdeps.json
new file mode 100644
index 0000000..2a2103a
--- /dev/null
+++ b/recipe_modules/checkout/tests/submodule.expected/submodule-try-multiple-cqdeps.json
@@ -0,0 +1,1054 @@
+[
+  {
+    "cmd": [],
+    "name": "checkout pigweed"
+  },
+  {
+    "cmd": [],
+    "name": "checkout pigweed.change data",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "checkout pigweed.change data.process gerrit changes",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "checkout pigweed.change data.process gerrit changes.0",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@3@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "checkout pigweed.change data.process gerrit changes.0.install infra/tools/luci/gerrit",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@4@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "ensure-directory",
+      "--mode",
+      "0777",
+      "[CACHE]/cipd/infra/tools/luci/gerrit/latest"
+    ],
+    "infra_step": true,
+    "name": "checkout pigweed.change data.process gerrit changes.0.install infra/tools/luci/gerrit.ensure package directory",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@5@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "cipd",
+      "ensure",
+      "-root",
+      "[CACHE]/cipd/infra/tools/luci/gerrit/latest",
+      "-ensure-file",
+      "infra/tools/luci/gerrit/${platform} latest",
+      "-max-threads",
+      "0",
+      "-json-output",
+      "/path/to/tmp/json"
+    ],
+    "infra_step": true,
+    "name": "checkout pigweed.change data.process gerrit changes.0.install infra/tools/luci/gerrit.ensure_installed",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@5@@@",
+      "@@@STEP_LOG_LINE@json.output@{@@@",
+      "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@    \"\": [@@@",
+      "@@@STEP_LOG_LINE@json.output@      {@@@",
+      "@@@STEP_LOG_LINE@json.output@        \"instance_id\": \"resolved-instance_id-of-latest----------\", @@@",
+      "@@@STEP_LOG_LINE@json.output@        \"package\": \"infra/tools/luci/gerrit/resolved-platform\"@@@",
+      "@@@STEP_LOG_LINE@json.output@      }@@@",
+      "@@@STEP_LOG_LINE@json.output@    ]@@@",
+      "@@@STEP_LOG_LINE@json.output@  }@@@",
+      "@@@STEP_LOG_LINE@json.output@}@@@",
+      "@@@STEP_LOG_END@json.output@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "[CACHE]/cipd/infra/tools/luci/gerrit/latest/gerrit",
+      "change-detail",
+      "-host",
+      "https://x-review.googlesource.com",
+      "-input",
+      "{\"change_id\": \"1234\"}",
+      "-output",
+      "/path/to/tmp/json"
+    ],
+    "name": "checkout pigweed.change data.process gerrit changes.0.details",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@4@@@",
+      "@@@STEP_LOG_LINE@json.output@{@@@",
+      "@@@STEP_LOG_LINE@json.output@  \"branch\": \"master\"@@@",
+      "@@@STEP_LOG_LINE@json.output@}@@@",
+      "@@@STEP_LOG_END@json.output@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "checkout pigweed.change data.process gerrit changes.resolve CL deps",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@3@@@",
+      "@@@STEP_LINK@x:2345@https://x-review.googlesource.com/2345@@@",
+      "@@@STEP_LINK@pigweed:3456@https://pigweed-review.googlesource.com/3456@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "[CACHE]/cipd/infra/tools/luci/gerrit/latest/gerrit",
+      "change-detail",
+      "-host",
+      "https://x-review.googlesource.com",
+      "-input",
+      "{\"change_id\": \"1234\", \"params\": {\"o\": [\"CURRENT_COMMIT\", \"CURRENT_REVISION\"]}}",
+      "-output",
+      "/path/to/tmp/json"
+    ],
+    "name": "checkout pigweed.change data.process gerrit changes.resolve CL deps.details x:1234",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@4@@@",
+      "@@@STEP_LOG_LINE@json.output@{@@@",
+      "@@@STEP_LOG_LINE@json.output@  \"_number\": 1234, @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"current_revision\": \"HASH\", @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"project\": \"project\", @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"revisions\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@    \"HASH\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@      \"_number\": 1, @@@",
+      "@@@STEP_LOG_LINE@json.output@      \"commit\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@        \"message\": \"Cq-Depend: x:2345,pigweed:3456\"@@@",
+      "@@@STEP_LOG_LINE@json.output@      }@@@",
+      "@@@STEP_LOG_LINE@json.output@    }@@@",
+      "@@@STEP_LOG_LINE@json.output@  }, @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"status\": \"NEW\"@@@",
+      "@@@STEP_LOG_LINE@json.output@}@@@",
+      "@@@STEP_LOG_END@json.output@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "checkout pigweed.change data.process gerrit changes.resolve CL deps.resolve deps for x:1234",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@4@@@",
+      "@@@STEP_LINK@x:2345@https://x-review.googlesource.com/2345@@@",
+      "@@@STEP_LINK@pigweed:3456@https://pigweed-review.googlesource.com/3456@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "[CACHE]/cipd/infra/tools/luci/gerrit/latest/gerrit",
+      "change-detail",
+      "-host",
+      "https://x-review.googlesource.com",
+      "-input",
+      "{\"change_id\": \"2345\", \"params\": {\"o\": [\"CURRENT_COMMIT\", \"CURRENT_REVISION\"]}}",
+      "-output",
+      "/path/to/tmp/json"
+    ],
+    "name": "checkout pigweed.change data.process gerrit changes.resolve CL deps.details x:2345",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@4@@@",
+      "@@@STEP_LOG_LINE@json.output@{@@@",
+      "@@@STEP_LOG_LINE@json.output@  \"_number\": 2345, @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"current_revision\": \"HASH\", @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"project\": \"bar\", @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"revisions\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@    \"HASH\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@      \"_number\": 1, @@@",
+      "@@@STEP_LOG_LINE@json.output@      \"commit\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@        \"message\": \"\"@@@",
+      "@@@STEP_LOG_LINE@json.output@      }@@@",
+      "@@@STEP_LOG_LINE@json.output@    }@@@",
+      "@@@STEP_LOG_LINE@json.output@  }, @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"status\": \"NEW\"@@@",
+      "@@@STEP_LOG_LINE@json.output@}@@@",
+      "@@@STEP_LOG_END@json.output@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "[CACHE]/cipd/infra/tools/luci/gerrit/latest/gerrit",
+      "change-detail",
+      "-host",
+      "https://pigweed-review.googlesource.com",
+      "-input",
+      "{\"change_id\": \"3456\", \"params\": {\"o\": [\"CURRENT_COMMIT\", \"CURRENT_REVISION\"]}}",
+      "-output",
+      "/path/to/tmp/json"
+    ],
+    "name": "checkout pigweed.change data.process gerrit changes.resolve CL deps.details pigweed:3456",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@4@@@",
+      "@@@STEP_LOG_LINE@json.output@{@@@",
+      "@@@STEP_LOG_LINE@json.output@  \"_number\": 3456, @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"current_revision\": \"HASH\", @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"project\": \"pigweed/pigweed\", @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"revisions\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@    \"HASH\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@      \"_number\": 1, @@@",
+      "@@@STEP_LOG_LINE@json.output@      \"commit\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@        \"message\": \"\"@@@",
+      "@@@STEP_LOG_LINE@json.output@      }@@@",
+      "@@@STEP_LOG_LINE@json.output@    }@@@",
+      "@@@STEP_LOG_LINE@json.output@  }, @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"status\": \"NEW\"@@@",
+      "@@@STEP_LOG_LINE@json.output@}@@@",
+      "@@@STEP_LOG_END@json.output@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "checkout pigweed.change data.process gerrit changes.resolve CL deps.resolve deps for pigweed:3456",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@4@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "checkout pigweed.change data.process gerrit changes.resolve CL deps.resolve deps for x:2345",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@4@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "checkout pigweed.change data.process gerrit changes.resolve CL deps.pass",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@4@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "[CACHE]/cipd/infra/tools/luci/gerrit/latest/gerrit",
+      "change-detail",
+      "-host",
+      "https://x-review.googlesource.com",
+      "-input",
+      "{\"change_id\": \"2345\"}",
+      "-output",
+      "/path/to/tmp/json"
+    ],
+    "name": "checkout pigweed.change data.process gerrit changes.details",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@3@@@",
+      "@@@STEP_LOG_LINE@json.output@{@@@",
+      "@@@STEP_LOG_LINE@json.output@  \"branch\": \"master\"@@@",
+      "@@@STEP_LOG_LINE@json.output@}@@@",
+      "@@@STEP_LOG_END@json.output@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "[CACHE]/cipd/infra/tools/luci/gerrit/latest/gerrit",
+      "change-detail",
+      "-host",
+      "https://pigweed-review.googlesource.com",
+      "-input",
+      "{\"change_id\": \"3456\"}",
+      "-output",
+      "/path/to/tmp/json"
+    ],
+    "name": "checkout pigweed.change data.process gerrit changes.details (2)",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@3@@@",
+      "@@@STEP_LOG_LINE@json.output@{@@@",
+      "@@@STEP_LOG_LINE@json.output@  \"branch\": \"master\"@@@",
+      "@@@STEP_LOG_LINE@json.output@}@@@",
+      "@@@STEP_LOG_END@json.output@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "checkout pigweed.change data.changes",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "checkout pigweed.change data.changes.x:1234",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@3@@@",
+      "@@@STEP_SUMMARY_TEXT@_Change(number=1234L, remote='https://x.googlesource.com/foo', ref='refs/changes/34/1234/1', rebase=True, branch='master', gerrit_name=u'x')@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "checkout pigweed.change data.changes.x:2345",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@3@@@",
+      "@@@STEP_SUMMARY_TEXT@_Change(number=2345, remote='https://x.googlesource.com/bar', ref='refs/changes/45/2345/1', rebase=True, branch='master', gerrit_name='x')@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "checkout pigweed.change data.changes.pigweed:3456",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@3@@@",
+      "@@@STEP_SUMMARY_TEXT@_Change(number=3456, remote='https://pigweed.googlesource.com/pigweed/pigweed', ref='refs/changes/56/3456/1', rebase=True, branch='master', gerrit_name='pigweed')@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "ensure-directory",
+      "--mode",
+      "0777",
+      "[START_DIR]/checkout"
+    ],
+    "infra_step": true,
+    "name": "checkout pigweed.makedirs",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "init"
+    ],
+    "cwd": "[START_DIR]/checkout",
+    "infra_step": true,
+    "name": "checkout pigweed.git init",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "remote",
+      "add",
+      "origin",
+      "https://pigweed.googlesource.com/pigweed/pigweed"
+    ],
+    "cwd": "[START_DIR]/checkout",
+    "infra_step": true,
+    "name": "checkout pigweed.git remote",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "config",
+      "fetch.uriprotocols",
+      "https"
+    ],
+    "cwd": "[START_DIR]/checkout",
+    "infra_step": true,
+    "name": "checkout pigweed.set fetch.uriprotocols",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "checkout pigweed.cache",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "ensure-directory",
+      "--mode",
+      "0777",
+      "[CACHE]/git/pigweed.googlesource.com-pigweed-pigweed"
+    ],
+    "cwd": "[START_DIR]/checkout",
+    "infra_step": true,
+    "name": "checkout pigweed.cache.makedirs",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "init",
+      "--bare"
+    ],
+    "cwd": "[CACHE]/git/pigweed.googlesource.com-pigweed-pigweed",
+    "infra_step": true,
+    "name": "checkout pigweed.cache.git init",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "config",
+      "remote.origin.url",
+      "https://pigweed.googlesource.com/pigweed/pigweed"
+    ],
+    "cwd": "[CACHE]/git/pigweed.googlesource.com-pigweed-pigweed",
+    "infra_step": true,
+    "name": "checkout pigweed.cache.remote set-url",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "config",
+      "fetch.uriprotocols",
+      "https"
+    ],
+    "cwd": "[CACHE]/git/pigweed.googlesource.com-pigweed-pigweed",
+    "infra_step": true,
+    "name": "checkout pigweed.cache.set fetch.uriprotocols",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "config",
+      "--replace-all",
+      "remote.origin.fetch",
+      "+refs/heads/*:refs/heads/*",
+      "\\+refs/heads/\\*:.*"
+    ],
+    "cwd": "[CACHE]/git/pigweed.googlesource.com-pigweed-pigweed",
+    "infra_step": true,
+    "name": "checkout pigweed.cache.replace fetch configs",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "fetch",
+      "--prune",
+      "--tags",
+      "origin"
+    ],
+    "cwd": "[CACHE]/git/pigweed.googlesource.com-pigweed-pigweed",
+    "infra_step": true,
+    "name": "checkout pigweed.cache.git fetch",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "ensure-directory",
+      "--mode",
+      "0777",
+      "[START_DIR]/checkout/.git/objects/info"
+    ],
+    "cwd": "[START_DIR]/checkout",
+    "infra_step": true,
+    "name": "checkout pigweed.cache.makedirs object/info",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "[CACHE]/git/pigweed.googlesource.com-pigweed-pigweed/objects\n",
+      "[START_DIR]/checkout/.git/objects/info/alternates"
+    ],
+    "cwd": "[START_DIR]/checkout",
+    "infra_step": true,
+    "name": "checkout pigweed.cache.alternates",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@",
+      "@@@STEP_LOG_LINE@alternates@[CACHE]/git/pigweed.googlesource.com-pigweed-pigweed/objects@@@",
+      "@@@STEP_LOG_END@alternates@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "fetch",
+      "--tags",
+      "origin",
+      "master",
+      "--recurse-submodules"
+    ],
+    "cwd": "[START_DIR]/checkout",
+    "infra_step": true,
+    "name": "checkout pigweed.git fetch",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "checkout",
+      "-f",
+      "FETCH_HEAD"
+    ],
+    "cwd": "[START_DIR]/checkout",
+    "infra_step": true,
+    "name": "checkout pigweed.git checkout",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "rev-parse",
+      "HEAD"
+    ],
+    "cwd": "[START_DIR]/checkout",
+    "infra_step": true,
+    "name": "checkout pigweed.git rev-parse",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "clean",
+      "-f",
+      "-d",
+      "-x"
+    ],
+    "cwd": "[START_DIR]/checkout",
+    "infra_step": true,
+    "name": "checkout pigweed.git clean",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "checkout pigweed.submodule",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "submodule",
+      "sync"
+    ],
+    "cwd": "[START_DIR]/checkout",
+    "infra_step": true,
+    "name": "checkout pigweed.submodule.git submodule sync",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "submodule",
+      "update",
+      "--init",
+      "--recursive"
+    ],
+    "cwd": "[START_DIR]/checkout",
+    "infra_step": true,
+    "name": "checkout pigweed.submodule.git submodule update",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "checkout pigweed.apply pigweed:3456",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@",
+      "@@@STEP_LINK@gerrit@https://pigweed-review.googlesource.com/c/3456@@@",
+      "@@@STEP_LINK@gitiles@https://pigweed.googlesource.com/pigweed/pigweed/+/refs/changes/56/3456/1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "fetch",
+      "https://pigweed.googlesource.com/pigweed/pigweed",
+      "refs/changes/56/3456/1"
+    ],
+    "cwd": "[START_DIR]/checkout",
+    "infra_step": true,
+    "name": "checkout pigweed.apply pigweed:3456.git fetch",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "checkout",
+      "--recurse-submodules",
+      "-b",
+      "working",
+      "FETCH_HEAD"
+    ],
+    "cwd": "[START_DIR]/checkout",
+    "infra_step": true,
+    "name": "checkout pigweed.apply pigweed:3456.git checkout patch",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "log",
+      "--oneline",
+      "-n",
+      "10"
+    ],
+    "cwd": "[START_DIR]/checkout",
+    "infra_step": true,
+    "name": "checkout pigweed.apply pigweed:3456.pre-rebase log",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "fetch",
+      "origin",
+      "master"
+    ],
+    "cwd": "[START_DIR]/checkout",
+    "infra_step": true,
+    "name": "checkout pigweed.apply pigweed:3456.git fetch (2)",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "rebase",
+      "origin/master"
+    ],
+    "cwd": "[START_DIR]/checkout",
+    "name": "checkout pigweed.apply pigweed:3456.git rebase",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "submodule",
+      "update"
+    ],
+    "cwd": "[START_DIR]/checkout",
+    "name": "checkout pigweed.apply pigweed:3456.git submodule",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "submodule",
+      "status",
+      "--recursive"
+    ],
+    "cwd": "[START_DIR]/checkout",
+    "name": "checkout pigweed.git submodule status",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "checkout pigweed.parse_submodules",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "checkout pigweed.parse_submodules.b/c/d",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@",
+      "@@@STEP_SUMMARY_TEXT@hash=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\ndescribe=foo@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "config",
+      "--get",
+      "remote.origin.url"
+    ],
+    "cwd": "[START_DIR]/checkout/b/c/d",
+    "name": "checkout pigweed.parse_submodules.git origin [START_DIR]/checkout/b/c/d",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "checkout pigweed.parse_submodules.foo",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@",
+      "@@@STEP_SUMMARY_TEXT@hash=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\ndescribe=foo@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "config",
+      "--get",
+      "remote.origin.url"
+    ],
+    "cwd": "[START_DIR]/checkout/foo",
+    "name": "checkout pigweed.parse_submodules.git origin [START_DIR]/checkout/foo",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "checkout pigweed.parse_submodules.baz",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@",
+      "@@@STEP_SUMMARY_TEXT@hash=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\ndescribe=foo@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "config",
+      "--get",
+      "remote.origin.url"
+    ],
+    "cwd": "[START_DIR]/checkout/baz",
+    "name": "checkout pigweed.parse_submodules.git origin [START_DIR]/checkout/baz",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "checkout pigweed.parse_submodules.bar",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@",
+      "@@@STEP_SUMMARY_TEXT@hash=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\ndescribe=foo@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "config",
+      "--get",
+      "remote.origin.url"
+    ],
+    "cwd": "[START_DIR]/checkout/bar",
+    "name": "checkout pigweed.parse_submodules.git origin [START_DIR]/checkout/bar",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "checkout pigweed.apply x:1234",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@",
+      "@@@STEP_LINK@gerrit@https://x-review.googlesource.com/c/1234@@@",
+      "@@@STEP_LINK@gitiles@https://x.googlesource.com/foo/+/refs/changes/34/1234/1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "fetch",
+      "https://x.googlesource.com/foo",
+      "refs/changes/34/1234/1"
+    ],
+    "cwd": "[START_DIR]/checkout/foo",
+    "infra_step": true,
+    "name": "checkout pigweed.apply x:1234.git fetch",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "checkout",
+      "--recurse-submodules",
+      "-b",
+      "working",
+      "FETCH_HEAD"
+    ],
+    "cwd": "[START_DIR]/checkout/foo",
+    "infra_step": true,
+    "name": "checkout pigweed.apply x:1234.git checkout patch",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "log",
+      "--oneline",
+      "-n",
+      "10"
+    ],
+    "cwd": "[START_DIR]/checkout/foo",
+    "infra_step": true,
+    "name": "checkout pigweed.apply x:1234.pre-rebase log",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "fetch",
+      "origin",
+      "master"
+    ],
+    "cwd": "[START_DIR]/checkout/foo",
+    "infra_step": true,
+    "name": "checkout pigweed.apply x:1234.git fetch (2)",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "rebase",
+      "origin/master"
+    ],
+    "cwd": "[START_DIR]/checkout/foo",
+    "name": "checkout pigweed.apply x:1234.git rebase",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "submodule",
+      "update"
+    ],
+    "cwd": "[START_DIR]/checkout/foo",
+    "name": "checkout pigweed.apply x:1234.git submodule",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "checkout pigweed.apply x:2345",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@",
+      "@@@STEP_LINK@gerrit@https://x-review.googlesource.com/c/2345@@@",
+      "@@@STEP_LINK@gitiles@https://x.googlesource.com/bar/+/refs/changes/45/2345/1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "fetch",
+      "https://x.googlesource.com/bar",
+      "refs/changes/45/2345/1"
+    ],
+    "cwd": "[START_DIR]/checkout/bar",
+    "infra_step": true,
+    "name": "checkout pigweed.apply x:2345.git fetch",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "checkout",
+      "--recurse-submodules",
+      "-b",
+      "working",
+      "FETCH_HEAD"
+    ],
+    "cwd": "[START_DIR]/checkout/bar",
+    "infra_step": true,
+    "name": "checkout pigweed.apply x:2345.git checkout patch",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "log",
+      "--oneline",
+      "-n",
+      "10"
+    ],
+    "cwd": "[START_DIR]/checkout/bar",
+    "infra_step": true,
+    "name": "checkout pigweed.apply x:2345.pre-rebase log",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "fetch",
+      "origin",
+      "master"
+    ],
+    "cwd": "[START_DIR]/checkout/bar",
+    "infra_step": true,
+    "name": "checkout pigweed.apply x:2345.git fetch (2)",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "rebase",
+      "origin/master"
+    ],
+    "cwd": "[START_DIR]/checkout/bar",
+    "name": "checkout pigweed.apply x:2345.git rebase",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "submodule",
+      "update"
+    ],
+    "cwd": "[START_DIR]/checkout/bar",
+    "name": "checkout pigweed.apply x:2345.git submodule",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "checkout pigweed.git log",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "log",
+      "--oneline",
+      "-n",
+      "10"
+    ],
+    "cwd": "[START_DIR]/checkout",
+    "name": "checkout pigweed.git log.[START_DIR]/checkout",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "log",
+      "--oneline",
+      "-n",
+      "10"
+    ],
+    "cwd": "[START_DIR]/checkout/b/c/d",
+    "name": "checkout pigweed.git log.[START_DIR]/checkout/b/c/d",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "log",
+      "--oneline",
+      "-n",
+      "10"
+    ],
+    "cwd": "[START_DIR]/checkout/foo",
+    "name": "checkout pigweed.git log.[START_DIR]/checkout/foo",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "log",
+      "--oneline",
+      "-n",
+      "10"
+    ],
+    "cwd": "[START_DIR]/checkout/baz",
+    "name": "checkout pigweed.git log.[START_DIR]/checkout/baz",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "log",
+      "--oneline",
+      "-n",
+      "10"
+    ],
+    "cwd": "[START_DIR]/checkout/bar",
+    "name": "checkout pigweed.git log.[START_DIR]/checkout/bar",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "name": "$result"
+  }
+]
\ No newline at end of file
diff --git a/recipe_modules/checkout/tests/submodule.expected/submodule-try-multiple-one-missing-one-forbidden-cqdeps.json b/recipe_modules/checkout/tests/submodule.expected/submodule-try-multiple-one-missing-one-forbidden-cqdeps.json
new file mode 100644
index 0000000..5cd38cd
--- /dev/null
+++ b/recipe_modules/checkout/tests/submodule.expected/submodule-try-multiple-one-missing-one-forbidden-cqdeps.json
@@ -0,0 +1,1024 @@
+[
+  {
+    "cmd": [],
+    "name": "checkout pigweed",
+    "~followup_annotations": [
+      "@@@STEP_SUMMARY_TEXT@Failed to apply the following CLs@@@",
+      "@@@STEP_LINK@x:2345@https://x-review.googlesource.com/c/2345@@@",
+      "@@@STEP_LINK@forbidden:9999@https://forbidden-review.googlesource.com/c/9999@@@",
+      "@@@STEP_WARNINGS@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "checkout pigweed.change data",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "checkout pigweed.change data.process gerrit changes",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "checkout pigweed.change data.process gerrit changes.0",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@3@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "checkout pigweed.change data.process gerrit changes.0.install infra/tools/luci/gerrit",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@4@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "ensure-directory",
+      "--mode",
+      "0777",
+      "[CACHE]/cipd/infra/tools/luci/gerrit/latest"
+    ],
+    "infra_step": true,
+    "name": "checkout pigweed.change data.process gerrit changes.0.install infra/tools/luci/gerrit.ensure package directory",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@5@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "cipd",
+      "ensure",
+      "-root",
+      "[CACHE]/cipd/infra/tools/luci/gerrit/latest",
+      "-ensure-file",
+      "infra/tools/luci/gerrit/${platform} latest",
+      "-max-threads",
+      "0",
+      "-json-output",
+      "/path/to/tmp/json"
+    ],
+    "infra_step": true,
+    "name": "checkout pigweed.change data.process gerrit changes.0.install infra/tools/luci/gerrit.ensure_installed",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@5@@@",
+      "@@@STEP_LOG_LINE@json.output@{@@@",
+      "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@    \"\": [@@@",
+      "@@@STEP_LOG_LINE@json.output@      {@@@",
+      "@@@STEP_LOG_LINE@json.output@        \"instance_id\": \"resolved-instance_id-of-latest----------\", @@@",
+      "@@@STEP_LOG_LINE@json.output@        \"package\": \"infra/tools/luci/gerrit/resolved-platform\"@@@",
+      "@@@STEP_LOG_LINE@json.output@      }@@@",
+      "@@@STEP_LOG_LINE@json.output@    ]@@@",
+      "@@@STEP_LOG_LINE@json.output@  }@@@",
+      "@@@STEP_LOG_LINE@json.output@}@@@",
+      "@@@STEP_LOG_END@json.output@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "[CACHE]/cipd/infra/tools/luci/gerrit/latest/gerrit",
+      "change-detail",
+      "-host",
+      "https://x-review.googlesource.com",
+      "-input",
+      "{\"change_id\": \"1234\"}",
+      "-output",
+      "/path/to/tmp/json"
+    ],
+    "name": "checkout pigweed.change data.process gerrit changes.0.details",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@4@@@",
+      "@@@STEP_LOG_LINE@json.output@{@@@",
+      "@@@STEP_LOG_LINE@json.output@  \"branch\": \"master\"@@@",
+      "@@@STEP_LOG_LINE@json.output@}@@@",
+      "@@@STEP_LOG_END@json.output@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "checkout pigweed.change data.process gerrit changes.resolve CL deps",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@3@@@",
+      "@@@STEP_LINK@x:2345@https://x-review.googlesource.com/2345@@@",
+      "@@@STEP_LINK@pigweed:3456@https://pigweed-review.googlesource.com/3456@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "[CACHE]/cipd/infra/tools/luci/gerrit/latest/gerrit",
+      "change-detail",
+      "-host",
+      "https://x-review.googlesource.com",
+      "-input",
+      "{\"change_id\": \"1234\", \"params\": {\"o\": [\"CURRENT_COMMIT\", \"CURRENT_REVISION\"]}}",
+      "-output",
+      "/path/to/tmp/json"
+    ],
+    "name": "checkout pigweed.change data.process gerrit changes.resolve CL deps.details x:1234",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@4@@@",
+      "@@@STEP_LOG_LINE@json.output@{@@@",
+      "@@@STEP_LOG_LINE@json.output@  \"_number\": 1234, @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"current_revision\": \"HASH\", @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"project\": \"project\", @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"revisions\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@    \"HASH\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@      \"_number\": 1, @@@",
+      "@@@STEP_LOG_LINE@json.output@      \"commit\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@        \"message\": \"Cq-Depend: x:2345,pigweed:3456,forbidden:9999\"@@@",
+      "@@@STEP_LOG_LINE@json.output@      }@@@",
+      "@@@STEP_LOG_LINE@json.output@    }@@@",
+      "@@@STEP_LOG_LINE@json.output@  }, @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"status\": \"NEW\"@@@",
+      "@@@STEP_LOG_LINE@json.output@}@@@",
+      "@@@STEP_LOG_END@json.output@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "checkout pigweed.change data.process gerrit changes.resolve CL deps.resolve deps for x:1234",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@4@@@",
+      "@@@STEP_LINK@x:2345@https://x-review.googlesource.com/2345@@@",
+      "@@@STEP_LINK@pigweed:3456@https://pigweed-review.googlesource.com/3456@@@",
+      "@@@STEP_LINK@forbidden:9999@https://forbidden-review.googlesource.com/9999@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "[CACHE]/cipd/infra/tools/luci/gerrit/latest/gerrit",
+      "change-detail",
+      "-host",
+      "https://x-review.googlesource.com",
+      "-input",
+      "{\"change_id\": \"2345\", \"params\": {\"o\": [\"CURRENT_COMMIT\", \"CURRENT_REVISION\"]}}",
+      "-output",
+      "/path/to/tmp/json"
+    ],
+    "name": "checkout pigweed.change data.process gerrit changes.resolve CL deps.details x:2345",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@4@@@",
+      "@@@STEP_LOG_LINE@json.output@{@@@",
+      "@@@STEP_LOG_LINE@json.output@  \"_number\": 2345, @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"current_revision\": \"HASH\", @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"project\": \"not-a-submodule\", @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"revisions\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@    \"HASH\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@      \"_number\": 1, @@@",
+      "@@@STEP_LOG_LINE@json.output@      \"commit\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@        \"message\": \"\"@@@",
+      "@@@STEP_LOG_LINE@json.output@      }@@@",
+      "@@@STEP_LOG_LINE@json.output@    }@@@",
+      "@@@STEP_LOG_LINE@json.output@  }, @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"status\": \"NEW\"@@@",
+      "@@@STEP_LOG_LINE@json.output@}@@@",
+      "@@@STEP_LOG_END@json.output@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "[CACHE]/cipd/infra/tools/luci/gerrit/latest/gerrit",
+      "change-detail",
+      "-host",
+      "https://pigweed-review.googlesource.com",
+      "-input",
+      "{\"change_id\": \"3456\", \"params\": {\"o\": [\"CURRENT_COMMIT\", \"CURRENT_REVISION\"]}}",
+      "-output",
+      "/path/to/tmp/json"
+    ],
+    "name": "checkout pigweed.change data.process gerrit changes.resolve CL deps.details pigweed:3456",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@4@@@",
+      "@@@STEP_LOG_LINE@json.output@{@@@",
+      "@@@STEP_LOG_LINE@json.output@  \"_number\": 3456, @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"current_revision\": \"HASH\", @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"project\": \"pigweed/pigweed\", @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"revisions\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@    \"HASH\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@      \"_number\": 1, @@@",
+      "@@@STEP_LOG_LINE@json.output@      \"commit\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@        \"message\": \"\"@@@",
+      "@@@STEP_LOG_LINE@json.output@      }@@@",
+      "@@@STEP_LOG_LINE@json.output@    }@@@",
+      "@@@STEP_LOG_LINE@json.output@  }, @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"status\": \"NEW\"@@@",
+      "@@@STEP_LOG_LINE@json.output@}@@@",
+      "@@@STEP_LOG_END@json.output@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "[CACHE]/cipd/infra/tools/luci/gerrit/latest/gerrit",
+      "change-detail",
+      "-host",
+      "https://forbidden-review.googlesource.com",
+      "-input",
+      "{\"change_id\": \"9999\", \"params\": {\"o\": [\"CURRENT_COMMIT\", \"CURRENT_REVISION\"]}}",
+      "-output",
+      "/path/to/tmp/json"
+    ],
+    "name": "checkout pigweed.change data.process gerrit changes.resolve CL deps.details forbidden:9999",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@4@@@",
+      "@@@STEP_LOG_LINE@json.output@{}@@@",
+      "@@@STEP_LOG_END@json.output@@@",
+      "@@@STEP_FAILURE@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "checkout pigweed.change data.process gerrit changes.resolve CL deps.resolve deps for pigweed:3456",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@4@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "checkout pigweed.change data.process gerrit changes.resolve CL deps.resolve deps for x:2345",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@4@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "checkout pigweed.change data.process gerrit changes.resolve CL deps.pass",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@4@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "[CACHE]/cipd/infra/tools/luci/gerrit/latest/gerrit",
+      "change-detail",
+      "-host",
+      "https://x-review.googlesource.com",
+      "-input",
+      "{\"change_id\": \"2345\"}",
+      "-output",
+      "/path/to/tmp/json"
+    ],
+    "name": "checkout pigweed.change data.process gerrit changes.details",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@3@@@",
+      "@@@STEP_LOG_LINE@json.output@{@@@",
+      "@@@STEP_LOG_LINE@json.output@  \"branch\": \"master\"@@@",
+      "@@@STEP_LOG_LINE@json.output@}@@@",
+      "@@@STEP_LOG_END@json.output@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "[CACHE]/cipd/infra/tools/luci/gerrit/latest/gerrit",
+      "change-detail",
+      "-host",
+      "https://pigweed-review.googlesource.com",
+      "-input",
+      "{\"change_id\": \"3456\"}",
+      "-output",
+      "/path/to/tmp/json"
+    ],
+    "name": "checkout pigweed.change data.process gerrit changes.details (2)",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@3@@@",
+      "@@@STEP_LOG_LINE@json.output@{@@@",
+      "@@@STEP_LOG_LINE@json.output@  \"branch\": \"master\"@@@",
+      "@@@STEP_LOG_LINE@json.output@}@@@",
+      "@@@STEP_LOG_END@json.output@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "checkout pigweed.change data.changes",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "checkout pigweed.change data.changes.x:1234",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@3@@@",
+      "@@@STEP_SUMMARY_TEXT@_Change(number=1234L, remote='https://x.googlesource.com/foo', ref='refs/changes/34/1234/1', rebase=True, branch='master', gerrit_name=u'x')@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "checkout pigweed.change data.changes.x:2345",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@3@@@",
+      "@@@STEP_SUMMARY_TEXT@_Change(number=2345, remote='https://x.googlesource.com/not-a-submodule', ref='refs/changes/45/2345/1', rebase=True, branch='master', gerrit_name='x')@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "checkout pigweed.change data.changes.pigweed:3456",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@3@@@",
+      "@@@STEP_SUMMARY_TEXT@_Change(number=3456, remote='https://pigweed.googlesource.com/pigweed/pigweed', ref='refs/changes/56/3456/1', rebase=True, branch='master', gerrit_name='pigweed')@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "checkout pigweed.change data.changes.forbidden:9999",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@3@@@",
+      "@@@STEP_SUMMARY_TEXT@_Change(number=9999, remote=None, ref=None, rebase=None, branch=None, gerrit_name='forbidden')@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "ensure-directory",
+      "--mode",
+      "0777",
+      "[START_DIR]/checkout"
+    ],
+    "infra_step": true,
+    "name": "checkout pigweed.makedirs",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "init"
+    ],
+    "cwd": "[START_DIR]/checkout",
+    "infra_step": true,
+    "name": "checkout pigweed.git init",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "remote",
+      "add",
+      "origin",
+      "https://pigweed.googlesource.com/pigweed/pigweed"
+    ],
+    "cwd": "[START_DIR]/checkout",
+    "infra_step": true,
+    "name": "checkout pigweed.git remote",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "config",
+      "fetch.uriprotocols",
+      "https"
+    ],
+    "cwd": "[START_DIR]/checkout",
+    "infra_step": true,
+    "name": "checkout pigweed.set fetch.uriprotocols",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "checkout pigweed.cache",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "ensure-directory",
+      "--mode",
+      "0777",
+      "[CACHE]/git/pigweed.googlesource.com-pigweed-pigweed"
+    ],
+    "cwd": "[START_DIR]/checkout",
+    "infra_step": true,
+    "name": "checkout pigweed.cache.makedirs",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "init",
+      "--bare"
+    ],
+    "cwd": "[CACHE]/git/pigweed.googlesource.com-pigweed-pigweed",
+    "infra_step": true,
+    "name": "checkout pigweed.cache.git init",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "config",
+      "remote.origin.url",
+      "https://pigweed.googlesource.com/pigweed/pigweed"
+    ],
+    "cwd": "[CACHE]/git/pigweed.googlesource.com-pigweed-pigweed",
+    "infra_step": true,
+    "name": "checkout pigweed.cache.remote set-url",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "config",
+      "fetch.uriprotocols",
+      "https"
+    ],
+    "cwd": "[CACHE]/git/pigweed.googlesource.com-pigweed-pigweed",
+    "infra_step": true,
+    "name": "checkout pigweed.cache.set fetch.uriprotocols",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "config",
+      "--replace-all",
+      "remote.origin.fetch",
+      "+refs/heads/*:refs/heads/*",
+      "\\+refs/heads/\\*:.*"
+    ],
+    "cwd": "[CACHE]/git/pigweed.googlesource.com-pigweed-pigweed",
+    "infra_step": true,
+    "name": "checkout pigweed.cache.replace fetch configs",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "fetch",
+      "--prune",
+      "--tags",
+      "origin"
+    ],
+    "cwd": "[CACHE]/git/pigweed.googlesource.com-pigweed-pigweed",
+    "infra_step": true,
+    "name": "checkout pigweed.cache.git fetch",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "ensure-directory",
+      "--mode",
+      "0777",
+      "[START_DIR]/checkout/.git/objects/info"
+    ],
+    "cwd": "[START_DIR]/checkout",
+    "infra_step": true,
+    "name": "checkout pigweed.cache.makedirs object/info",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "[CACHE]/git/pigweed.googlesource.com-pigweed-pigweed/objects\n",
+      "[START_DIR]/checkout/.git/objects/info/alternates"
+    ],
+    "cwd": "[START_DIR]/checkout",
+    "infra_step": true,
+    "name": "checkout pigweed.cache.alternates",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@",
+      "@@@STEP_LOG_LINE@alternates@[CACHE]/git/pigweed.googlesource.com-pigweed-pigweed/objects@@@",
+      "@@@STEP_LOG_END@alternates@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "fetch",
+      "--tags",
+      "origin",
+      "master",
+      "--recurse-submodules"
+    ],
+    "cwd": "[START_DIR]/checkout",
+    "infra_step": true,
+    "name": "checkout pigweed.git fetch",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "checkout",
+      "-f",
+      "FETCH_HEAD"
+    ],
+    "cwd": "[START_DIR]/checkout",
+    "infra_step": true,
+    "name": "checkout pigweed.git checkout",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "rev-parse",
+      "HEAD"
+    ],
+    "cwd": "[START_DIR]/checkout",
+    "infra_step": true,
+    "name": "checkout pigweed.git rev-parse",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "clean",
+      "-f",
+      "-d",
+      "-x"
+    ],
+    "cwd": "[START_DIR]/checkout",
+    "infra_step": true,
+    "name": "checkout pigweed.git clean",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "checkout pigweed.submodule",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "submodule",
+      "sync"
+    ],
+    "cwd": "[START_DIR]/checkout",
+    "infra_step": true,
+    "name": "checkout pigweed.submodule.git submodule sync",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "submodule",
+      "update",
+      "--init",
+      "--recursive"
+    ],
+    "cwd": "[START_DIR]/checkout",
+    "infra_step": true,
+    "name": "checkout pigweed.submodule.git submodule update",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "checkout pigweed.apply pigweed:3456",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@",
+      "@@@STEP_LINK@gerrit@https://pigweed-review.googlesource.com/c/3456@@@",
+      "@@@STEP_LINK@gitiles@https://pigweed.googlesource.com/pigweed/pigweed/+/refs/changes/56/3456/1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "fetch",
+      "https://pigweed.googlesource.com/pigweed/pigweed",
+      "refs/changes/56/3456/1"
+    ],
+    "cwd": "[START_DIR]/checkout",
+    "infra_step": true,
+    "name": "checkout pigweed.apply pigweed:3456.git fetch",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "checkout",
+      "--recurse-submodules",
+      "-b",
+      "working",
+      "FETCH_HEAD"
+    ],
+    "cwd": "[START_DIR]/checkout",
+    "infra_step": true,
+    "name": "checkout pigweed.apply pigweed:3456.git checkout patch",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "log",
+      "--oneline",
+      "-n",
+      "10"
+    ],
+    "cwd": "[START_DIR]/checkout",
+    "infra_step": true,
+    "name": "checkout pigweed.apply pigweed:3456.pre-rebase log",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "fetch",
+      "origin",
+      "master"
+    ],
+    "cwd": "[START_DIR]/checkout",
+    "infra_step": true,
+    "name": "checkout pigweed.apply pigweed:3456.git fetch (2)",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "rebase",
+      "origin/master"
+    ],
+    "cwd": "[START_DIR]/checkout",
+    "name": "checkout pigweed.apply pigweed:3456.git rebase",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "submodule",
+      "update"
+    ],
+    "cwd": "[START_DIR]/checkout",
+    "name": "checkout pigweed.apply pigweed:3456.git submodule",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "submodule",
+      "status",
+      "--recursive"
+    ],
+    "cwd": "[START_DIR]/checkout",
+    "name": "checkout pigweed.git submodule status",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "checkout pigweed.parse_submodules",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "checkout pigweed.parse_submodules.b/c/d",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@",
+      "@@@STEP_SUMMARY_TEXT@hash=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\ndescribe=foo@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "config",
+      "--get",
+      "remote.origin.url"
+    ],
+    "cwd": "[START_DIR]/checkout/b/c/d",
+    "name": "checkout pigweed.parse_submodules.git origin [START_DIR]/checkout/b/c/d",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "checkout pigweed.parse_submodules.foo",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@",
+      "@@@STEP_SUMMARY_TEXT@hash=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\ndescribe=foo@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "config",
+      "--get",
+      "remote.origin.url"
+    ],
+    "cwd": "[START_DIR]/checkout/foo",
+    "name": "checkout pigweed.parse_submodules.git origin [START_DIR]/checkout/foo",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "checkout pigweed.parse_submodules.baz",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@",
+      "@@@STEP_SUMMARY_TEXT@hash=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\ndescribe=foo@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "config",
+      "--get",
+      "remote.origin.url"
+    ],
+    "cwd": "[START_DIR]/checkout/baz",
+    "name": "checkout pigweed.parse_submodules.git origin [START_DIR]/checkout/baz",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "checkout pigweed.parse_submodules.bar",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@",
+      "@@@STEP_SUMMARY_TEXT@hash=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\ndescribe=foo@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "config",
+      "--get",
+      "remote.origin.url"
+    ],
+    "cwd": "[START_DIR]/checkout/bar",
+    "name": "checkout pigweed.parse_submodules.git origin [START_DIR]/checkout/bar",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "checkout pigweed.apply x:1234",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@",
+      "@@@STEP_LINK@gerrit@https://x-review.googlesource.com/c/1234@@@",
+      "@@@STEP_LINK@gitiles@https://x.googlesource.com/foo/+/refs/changes/34/1234/1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "fetch",
+      "https://x.googlesource.com/foo",
+      "refs/changes/34/1234/1"
+    ],
+    "cwd": "[START_DIR]/checkout/foo",
+    "infra_step": true,
+    "name": "checkout pigweed.apply x:1234.git fetch",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "checkout",
+      "--recurse-submodules",
+      "-b",
+      "working",
+      "FETCH_HEAD"
+    ],
+    "cwd": "[START_DIR]/checkout/foo",
+    "infra_step": true,
+    "name": "checkout pigweed.apply x:1234.git checkout patch",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "log",
+      "--oneline",
+      "-n",
+      "10"
+    ],
+    "cwd": "[START_DIR]/checkout/foo",
+    "infra_step": true,
+    "name": "checkout pigweed.apply x:1234.pre-rebase log",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "fetch",
+      "origin",
+      "master"
+    ],
+    "cwd": "[START_DIR]/checkout/foo",
+    "infra_step": true,
+    "name": "checkout pigweed.apply x:1234.git fetch (2)",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "rebase",
+      "origin/master"
+    ],
+    "cwd": "[START_DIR]/checkout/foo",
+    "name": "checkout pigweed.apply x:1234.git rebase",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "submodule",
+      "update"
+    ],
+    "cwd": "[START_DIR]/checkout/foo",
+    "name": "checkout pigweed.apply x:1234.git submodule",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "checkout pigweed.some changes were not applied",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@",
+      "@@@STEP_WARNINGS@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "checkout pigweed.some changes were not applied.failed to apply x:2345",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@",
+      "@@@STEP_LINK@gerrit@https://x-review.googlesource.com/c/2345@@@",
+      "@@@STEP_LINK@gitiles@https://x.googlesource.com/not-a-submodule/+/refs/changes/45/2345/1@@@",
+      "@@@STEP_WARNINGS@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "checkout pigweed.some changes were not applied.failed to apply forbidden:9999",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@",
+      "@@@STEP_LINK@gerrit@https://forbidden-review.googlesource.com/c/9999@@@",
+      "@@@STEP_LINK@gitiles@None/+/None@@@",
+      "@@@STEP_WARNINGS@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "checkout pigweed.git log",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "log",
+      "--oneline",
+      "-n",
+      "10"
+    ],
+    "cwd": "[START_DIR]/checkout",
+    "name": "checkout pigweed.git log.[START_DIR]/checkout",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "log",
+      "--oneline",
+      "-n",
+      "10"
+    ],
+    "cwd": "[START_DIR]/checkout/b/c/d",
+    "name": "checkout pigweed.git log.[START_DIR]/checkout/b/c/d",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "log",
+      "--oneline",
+      "-n",
+      "10"
+    ],
+    "cwd": "[START_DIR]/checkout/foo",
+    "name": "checkout pigweed.git log.[START_DIR]/checkout/foo",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "log",
+      "--oneline",
+      "-n",
+      "10"
+    ],
+    "cwd": "[START_DIR]/checkout/baz",
+    "name": "checkout pigweed.git log.[START_DIR]/checkout/baz",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "log",
+      "--oneline",
+      "-n",
+      "10"
+    ],
+    "cwd": "[START_DIR]/checkout/bar",
+    "name": "checkout pigweed.git log.[START_DIR]/checkout/bar",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "name": "$result"
+  }
+]
\ No newline at end of file
diff --git a/recipe_modules/checkout/tests/submodule.expected/submodule-try-multiple-one-missing.json b/recipe_modules/checkout/tests/submodule.expected/submodule-try-multiple-one-missing.json
index d21d83c..9d3052b 100644
--- a/recipe_modules/checkout/tests/submodule.expected/submodule-try-multiple-one-missing.json
+++ b/recipe_modules/checkout/tests/submodule.expected/submodule-try-multiple-one-missing.json
@@ -3,6 +3,8 @@
     "cmd": [],
     "name": "checkout pigweed",
     "~followup_annotations": [
+      "@@@STEP_SUMMARY_TEXT@Failed to apply the following CLs@@@",
+      "@@@STEP_LINK@x:2345@https://x-review.googlesource.com/c/2345@@@",
       "@@@STEP_WARNINGS@@@"
     ]
   },
@@ -158,6 +160,57 @@
   },
   {
     "cmd": [],
+    "name": "checkout pigweed.change data.process gerrit changes.resolve CL deps",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@3@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "[CACHE]/cipd/infra/tools/luci/gerrit/latest/gerrit",
+      "change-detail",
+      "-host",
+      "https://pigweed-review.googlesource.com",
+      "-input",
+      "{\"change_id\": \"3456\", \"params\": {\"o\": [\"CURRENT_COMMIT\", \"CURRENT_REVISION\"]}}",
+      "-output",
+      "/path/to/tmp/json"
+    ],
+    "name": "checkout pigweed.change data.process gerrit changes.resolve CL deps.details pigweed:3456",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@4@@@",
+      "@@@STEP_LOG_LINE@json.output@{@@@",
+      "@@@STEP_LOG_LINE@json.output@  \"current_revision\": \"HASH\", @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"project\": \"project\", @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"revisions\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@    \"HASH\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@      \"_number\": 1, @@@",
+      "@@@STEP_LOG_LINE@json.output@      \"commit\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@        \"message\": \"\"@@@",
+      "@@@STEP_LOG_LINE@json.output@      }@@@",
+      "@@@STEP_LOG_LINE@json.output@    }@@@",
+      "@@@STEP_LOG_LINE@json.output@  }, @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"status\": \"NEW\"@@@",
+      "@@@STEP_LOG_LINE@json.output@}@@@",
+      "@@@STEP_LOG_END@json.output@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "checkout pigweed.change data.process gerrit changes.resolve CL deps.resolve deps for pigweed:3456",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@4@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "checkout pigweed.change data.process gerrit changes.resolve CL deps.pass",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@4@@@"
+    ]
+  },
+  {
+    "cmd": [],
     "name": "checkout pigweed.change data.changes",
     "~followup_annotations": [
       "@@@STEP_NEST_LEVEL@2@@@"
diff --git a/recipe_modules/checkout/tests/submodule.expected/submodule-try-multiple.json b/recipe_modules/checkout/tests/submodule.expected/submodule-try-multiple.json
index bdce250..7765e6b 100644
--- a/recipe_modules/checkout/tests/submodule.expected/submodule-try-multiple.json
+++ b/recipe_modules/checkout/tests/submodule.expected/submodule-try-multiple.json
@@ -155,6 +155,57 @@
   },
   {
     "cmd": [],
+    "name": "checkout pigweed.change data.process gerrit changes.resolve CL deps",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@3@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "[CACHE]/cipd/infra/tools/luci/gerrit/latest/gerrit",
+      "change-detail",
+      "-host",
+      "https://pigweed-review.googlesource.com",
+      "-input",
+      "{\"change_id\": \"3456\", \"params\": {\"o\": [\"CURRENT_COMMIT\", \"CURRENT_REVISION\"]}}",
+      "-output",
+      "/path/to/tmp/json"
+    ],
+    "name": "checkout pigweed.change data.process gerrit changes.resolve CL deps.details pigweed:3456",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@4@@@",
+      "@@@STEP_LOG_LINE@json.output@{@@@",
+      "@@@STEP_LOG_LINE@json.output@  \"current_revision\": \"HASH\", @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"project\": \"project\", @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"revisions\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@    \"HASH\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@      \"_number\": 1, @@@",
+      "@@@STEP_LOG_LINE@json.output@      \"commit\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@        \"message\": \"\"@@@",
+      "@@@STEP_LOG_LINE@json.output@      }@@@",
+      "@@@STEP_LOG_LINE@json.output@    }@@@",
+      "@@@STEP_LOG_LINE@json.output@  }, @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"status\": \"NEW\"@@@",
+      "@@@STEP_LOG_LINE@json.output@}@@@",
+      "@@@STEP_LOG_END@json.output@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "checkout pigweed.change data.process gerrit changes.resolve CL deps.resolve deps for pigweed:3456",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@4@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "checkout pigweed.change data.process gerrit changes.resolve CL deps.pass",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@4@@@"
+    ]
+  },
+  {
+    "cmd": [],
     "name": "checkout pigweed.change data.changes",
     "~followup_annotations": [
       "@@@STEP_NEST_LEVEL@2@@@"
diff --git a/recipe_modules/checkout/tests/submodule.expected/submodule-try-not-found.json b/recipe_modules/checkout/tests/submodule.expected/submodule-try-not-found.json
index d00bba3..0419ed4 100644
--- a/recipe_modules/checkout/tests/submodule.expected/submodule-try-not-found.json
+++ b/recipe_modules/checkout/tests/submodule.expected/submodule-try-not-found.json
@@ -104,6 +104,57 @@
   },
   {
     "cmd": [],
+    "name": "checkout pigweed.change data.process gerrit changes.resolve CL deps",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@3@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "[CACHE]/cipd/infra/tools/luci/gerrit/latest/gerrit",
+      "change-detail",
+      "-host",
+      "https://x-review.googlesource.com",
+      "-input",
+      "{\"change_id\": \"123456\", \"params\": {\"o\": [\"CURRENT_COMMIT\", \"CURRENT_REVISION\"]}}",
+      "-output",
+      "/path/to/tmp/json"
+    ],
+    "name": "checkout pigweed.change data.process gerrit changes.resolve CL deps.details x:123456",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@4@@@",
+      "@@@STEP_LOG_LINE@json.output@{@@@",
+      "@@@STEP_LOG_LINE@json.output@  \"current_revision\": \"HASH\", @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"project\": \"project\", @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"revisions\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@    \"HASH\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@      \"_number\": 1, @@@",
+      "@@@STEP_LOG_LINE@json.output@      \"commit\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@        \"message\": \"\"@@@",
+      "@@@STEP_LOG_LINE@json.output@      }@@@",
+      "@@@STEP_LOG_LINE@json.output@    }@@@",
+      "@@@STEP_LOG_LINE@json.output@  }, @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"status\": \"NEW\"@@@",
+      "@@@STEP_LOG_LINE@json.output@}@@@",
+      "@@@STEP_LOG_END@json.output@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "checkout pigweed.change data.process gerrit changes.resolve CL deps.resolve deps for x:123456",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@4@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "checkout pigweed.change data.process gerrit changes.resolve CL deps.pass",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@4@@@"
+    ]
+  },
+  {
+    "cmd": [],
     "name": "checkout pigweed.change data.changes",
     "~followup_annotations": [
       "@@@STEP_NEST_LEVEL@2@@@"
diff --git a/recipe_modules/checkout/tests/submodule.expected/submodule-try.json b/recipe_modules/checkout/tests/submodule.expected/submodule-try.json
index b38b0f0..b8687c4 100644
--- a/recipe_modules/checkout/tests/submodule.expected/submodule-try.json
+++ b/recipe_modules/checkout/tests/submodule.expected/submodule-try.json
@@ -101,6 +101,57 @@
   },
   {
     "cmd": [],
+    "name": "checkout pigweed.change data.process gerrit changes.resolve CL deps",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@3@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "[CACHE]/cipd/infra/tools/luci/gerrit/latest/gerrit",
+      "change-detail",
+      "-host",
+      "https://x-review.googlesource.com",
+      "-input",
+      "{\"change_id\": \"123456\", \"params\": {\"o\": [\"CURRENT_COMMIT\", \"CURRENT_REVISION\"]}}",
+      "-output",
+      "/path/to/tmp/json"
+    ],
+    "name": "checkout pigweed.change data.process gerrit changes.resolve CL deps.details x:123456",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@4@@@",
+      "@@@STEP_LOG_LINE@json.output@{@@@",
+      "@@@STEP_LOG_LINE@json.output@  \"current_revision\": \"HASH\", @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"project\": \"project\", @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"revisions\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@    \"HASH\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@      \"_number\": 1, @@@",
+      "@@@STEP_LOG_LINE@json.output@      \"commit\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@        \"message\": \"\"@@@",
+      "@@@STEP_LOG_LINE@json.output@      }@@@",
+      "@@@STEP_LOG_LINE@json.output@    }@@@",
+      "@@@STEP_LOG_LINE@json.output@  }, @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"status\": \"NEW\"@@@",
+      "@@@STEP_LOG_LINE@json.output@}@@@",
+      "@@@STEP_LOG_END@json.output@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "checkout pigweed.change data.process gerrit changes.resolve CL deps.resolve deps for x:123456",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@4@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "checkout pigweed.change data.process gerrit changes.resolve CL deps.pass",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@4@@@"
+    ]
+  },
+  {
+    "cmd": [],
     "name": "checkout pigweed.change data.changes",
     "~followup_annotations": [
       "@@@STEP_NEST_LEVEL@2@@@"
diff --git a/recipe_modules/checkout/tests/submodule.py b/recipe_modules/checkout/tests/submodule.py
index 021e1ac..e6e73df 100644
--- a/recipe_modules/checkout/tests/submodule.py
+++ b/recipe_modules/checkout/tests/submodule.py
@@ -16,6 +16,7 @@
 DEPS = [
     'fuchsia/status_check',
     'pigweed/checkout',
+    'pigweed/cq_deps',
     'recipe_engine/properties',
 ]
 
@@ -25,13 +26,13 @@
 
 
 def GenTests(api):  # pylint: disable=invalid-name
-    def props(
-        remote='https://pigweed.googlesource.com/pigweed/pigweed.git',
-        branch=None,
-    ):
-        return api.properties(
-            **api.checkout.git_properties(remote=remote, branch=branch)
+    def props(**kwargs):
+        kwargs.setdefault(
+            'remote', 'https://pigweed.googlesource.com/pigweed/pigweed.git'
         )
+        kwargs.setdefault('branch', None)
+
+        return api.properties(**api.checkout.git_properties(**kwargs))
 
     submodule_data = api.checkout.submodules(
         foo='https://x.googlesource.com/foo',
@@ -114,3 +115,74 @@
         + api.checkout.change_not_applied('x:2345')
         + api.checkout.change_applied('pigweed:3456')
     )
+
+    # These tests are nominally identical to 'submodule-try-multiple' and
+    # 'submodule-try-multiple-one-missing' above but use the cq_deps module.
+    # After the 'process gerrit changes' step all changes are minor (e.g.
+    # '2345' instead of '2345L').
+    yield (
+        api.status_check.test('submodule-try-multiple-cqdeps')
+        + props()
+        + api.checkout.try_test_data(
+            git_repo='https://x.googlesource.com/foo',
+            change_number=1234,
+            patch_set=1,
+        )
+        + api.cq_deps.details(
+            'x:1234',
+            message='Cq-Depend: x:2345,pigweed:3456',
+            prefix='checkout pigweed.change data.process gerrit changes.',
+        )
+        + api.cq_deps.details(
+            'x:2345',
+            project='bar',
+            prefix='checkout pigweed.change data.process gerrit changes.',
+        )
+        + api.cq_deps.details(
+            'pigweed:3456',
+            project='pigweed/pigweed',
+            prefix='checkout pigweed.change data.process gerrit changes.',
+        )
+        + submodule_data
+        + api.checkout.all_changes_applied()
+        + api.checkout.change_applied('x:1234')
+        + api.checkout.change_applied('x:2345')
+        + api.checkout.change_applied('pigweed:3456')
+    )
+
+    yield (
+        api.status_check.test(
+            'submodule-try-multiple-one-missing-one-forbidden-cqdeps'
+        )
+        + props()
+        + api.checkout.try_test_data(
+            git_repo='https://x.googlesource.com/foo',
+            change_number=1234,
+            patch_set=1,
+        )
+        + api.cq_deps.details(
+            'x:1234',
+            message='Cq-Depend: x:2345,pigweed:3456,forbidden:9999',
+            prefix='checkout pigweed.change data.process gerrit changes.',
+        )
+        + api.cq_deps.details(
+            'x:2345',
+            project='not-a-submodule',
+            prefix='checkout pigweed.change data.process gerrit changes.',
+        )
+        + api.cq_deps.details(
+            'pigweed:3456',
+            project='pigweed/pigweed',
+            prefix='checkout pigweed.change data.process gerrit changes.',
+        )
+        + api.cq_deps.forbidden(
+            'forbidden:9999',
+            prefix='checkout pigweed.change data.process gerrit changes.',
+        )
+        + submodule_data
+        + api.checkout.some_changes_applied()
+        + api.checkout.change_applied('x:1234')
+        + api.checkout.change_not_applied('x:2345')
+        + api.checkout.change_not_applied('forbidden:9999')
+        + api.checkout.change_applied('pigweed:3456')
+    )
diff --git a/recipe_modules/cq_deps/__init__.py b/recipe_modules/cq_deps/__init__.py
new file mode 100644
index 0000000..35fe50d
--- /dev/null
+++ b/recipe_modules/cq_deps/__init__.py
@@ -0,0 +1,23 @@
+# Copyright 2020 The Pigweed Authors
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may not
+# use this file except in compliance with the License. You may obtain a copy of
+# the License at
+#
+#     https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations under
+# the License.
+
+# pylint: disable=missing-docstring
+
+DEPS = [
+    'fuchsia/gerrit',
+    'fuchsia/git',
+    'fuchsia/gitiles',
+    'recipe_engine/json',
+    'recipe_engine/step',
+]
diff --git a/recipe_modules/cq_deps/api.py b/recipe_modules/cq_deps/api.py
new file mode 100644
index 0000000..c42c4af
--- /dev/null
+++ b/recipe_modules/cq_deps/api.py
@@ -0,0 +1,204 @@
+# Copyright 2020 The Pigweed Authors
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may not
+# use this file except in compliance with the License. You may obtain a copy of
+# the License at
+#
+#     https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations under
+# the License.
+"""Find dependencies on pending CLs."""
+
+import re
+import urlparse
+
+import attr
+from recipe_engine import recipe_api
+
+
+@attr.s
+class Change(object):
+    """Details of a matching change.
+
+    This is meant to match the GerritChange message defined at
+    https://source.chromium.org/chromium/infra/infra/+/master:go/src/go.chromium.org/luci/buildbucket/proto/common.proto;l=135?q=GerritChange
+    A few fields have been added for use by this module.
+    """
+
+    gerrit_name = attr.ib(type=str)
+    change = attr.ib(type=int)
+    project = attr.ib(type=str, default=None)
+    patchset = attr.ib(type=int, default=None)
+    status = attr.ib(type=str, default=None)
+
+    @property
+    def host(self):
+        return '{}-review.googlesource.com'.format(self.gerrit_name)
+
+    @property
+    def link(self):
+        return 'https://{}/{}'.format(self.host, self.change)
+
+    @property
+    def name(self):
+        return '{}:{}'.format(self.gerrit_name, self.change)
+
+    def __str__(self):
+        return self.name
+
+
+class CqDepsApi(recipe_api.RecipeApi):
+    """Find dependencies on pending CLs."""
+
+    def __init__(self, *args, **kwargs):
+        super(CqDepsApi, self).__init__(*args, **kwargs)
+        self._details = {}
+
+    def resolve(self, gerrit_name, number, statuses=('NEW',)):
+        """Recursively resolve dependencies of a CL.
+
+        Resolve dependencies of a CL by parsing its commit message. Looks for
+        lines like below, and only in the last "paragraph".
+
+        Cq-Depend: gerritname:1, commas-supported:2
+        Cq-Depend: name:4,spaces-not-required:3
+        Cq-Depend: name:5,,,,extra-commas-ignored:6,,,,
+
+        Args:
+            gerrit_name (str): Name of Gerrit host of initial change.
+            number (int|str): Gerrit number or git commit hash of initial
+                change.
+            statuses (seq(str)): CL statuses to accept.
+
+        Returns a tuple of resolved Change objects (excluding the given change)
+        and unresolved Change objects (which are not completely populated).
+        """
+
+        with self.m.step.nest('resolve CL deps', status='last') as pres:
+            if isinstance(number, str):
+                query_results = self.m.gerrit.change_query(
+                    'number',
+                    dict(commit=number),
+                    host='{}-review.googlesource.com'.format(gerrit_name),
+                    test_data=self.m.json.test_api.output([{'_number': 1}]),
+                ).json.output
+                number = query_results[0]['_number']
+
+            deps = {}
+            unresolved = []
+            initial_change = Change(gerrit_name, number)
+            to_process = [initial_change]
+            # Want all initial details calls to come from this function so
+            # their step names are predictable. Further calls can be made
+            # anywhere and will hit the cache.
+            _ = self._change_details(initial_change)
+
+            while to_process:
+                current = to_process.pop()
+                deps[current.name] = current
+
+                for dep in self._resolve(current):
+                    if dep.name not in deps:
+                        details = self._change_details(dep)
+                        if not details:
+                            unresolved.append(dep)
+                            continue
+                        if details['status'] not in statuses:
+                            continue
+
+                        to_process.append(dep)
+                        deps[dep.name] = dep
+
+            del deps[initial_change.name]
+            for dep in deps.itervalues():
+                pres.links[dep.name] = dep.link
+
+            # Make sure the last step passes because _change_details might fail
+            # for an individual CL in ways we don't care about.
+            with self.m.step.nest('pass'):
+                pass
+
+            return deps.values(), unresolved
+
+    def _resolve(self, change):
+        """Resolve dependencies of the given change.
+
+        Retrieve and parse the commit message of the given change, extracting
+        from "Cq-Depend:" lines (see resolve function above for details). Does
+        not attempt to verify if these dependent changes are accessible or
+        even valid.
+
+        Args:
+            change (Change): Change to resolve dependencies for.
+
+        Returns a list of changes on which the given change depends.
+        """
+
+        with self.m.step.nest('resolve deps for {}'.format(change)) as pres:
+            result = self._change_details(change)
+            deps = []
+
+            current_revision = result['revisions'][result['current_revision']]
+            commit_message = current_revision['commit']['message']
+            last_paragraph = commit_message.split('\n\n')[-1]
+
+            for line in last_paragraph.split('\n'):
+                if ':' not in line:
+                    continue
+                name, value = line.split(':', 1)
+
+                if name != 'Cq-Depend':
+                    continue
+
+                values = value.split(',')
+                for val in values:
+                    val = val.strip()
+                    if not val:
+                        continue
+                    gerrit_name, number = val.split(':')
+                    deps.append(Change(gerrit_name, int(number)))
+
+            for dep in deps:
+                pres.links[dep.name] = dep.link
+            return deps
+
+    def _change_details(self, change):
+        if change.name in self._details:
+            return self._details[change.name]
+
+        try:
+            step = self.m.gerrit.change_details(
+                'details {}'.format(change),
+                change_id=str(change.change),
+                host=change.host,
+                query_params=['CURRENT_COMMIT', 'CURRENT_REVISION',],
+                max_attempts=5,
+                test_data=self.m.json.test_api.output(
+                    {
+                        'current_revision': 'HASH',
+                        'revisions': {
+                            'HASH': {'_number': 1, 'commit': {'message': ''}}
+                        },
+                        'project': 'project',
+                        'status': 'NEW',
+                    }
+                ),
+            )
+            details = step.json.output
+
+        except self.m.step.StepFailure:
+            details = {}
+
+        self._details[change.name] = details
+        if details:
+            change.project = details['project']
+            change.patchset = details['revisions'][details['current_revision']][
+                '_number'
+            ]
+            change.status = details['status']
+
+        return self._details[change.name]
diff --git a/recipe_modules/cq_deps/test_api.py b/recipe_modules/cq_deps/test_api.py
new file mode 100644
index 0000000..a90c1e6
--- /dev/null
+++ b/recipe_modules/cq_deps/test_api.py
@@ -0,0 +1,86 @@
+# Copyright 2019 The Pigweed Authors
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may not
+# use this file except in compliance with the License. You may obtain a copy of
+# the License at
+#
+#     https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations under
+# the License.
+"""Test API for cq_deps."""
+
+from recipe_engine import post_process, recipe_test_api
+
+
+class CqDepsTestApi(recipe_test_api.RecipeTestApi):
+    def _step_name(self, prefix, name, n):
+        return '{}resolve CL deps.details {}{}'.format(
+            prefix, name, ' ({})'.format(n) if n else '',
+        )
+
+    def details(
+        self,
+        name,
+        message='',
+        status='NEW',
+        project='project',
+        patchset=1,
+        prefix='',
+        retcode=0,
+        n=None,
+    ):
+        return self.override_step_data(
+            self._step_name(prefix, name, n),
+            self.m.json.output(
+                {
+                    '_number': int(name.split(':')[1]),
+                    'current_revision': 'HASH',
+                    'project': project,
+                    'revisions': {
+                        'HASH': {
+                            '_number': patchset,
+                            'commit': {'message': message},
+                        }
+                    },
+                    'status': status,
+                }
+            ),
+        )
+
+    def transient(self, name, prefix='', n=None):
+        return self.override_step_data(
+            self._step_name(prefix, name, n),
+            self.m.json.output({'transient': True}),
+            retcode=1,
+        )
+
+    def forbidden(self, name, prefix='', n=None):
+        return self.override_step_data(
+            self._step_name(prefix, name, n), self.m.json.output({}), retcode=1,
+        )
+
+    def has_deps(self, *names):
+        results = []
+        for name in names:
+            results.append(
+                self.post_process(
+                    post_process.MustRun, 'final deps.{}'.format(name),
+                )
+            )
+
+        return sum(results[1:], results[0])
+
+    def lacks_deps(self, *names):
+        results = []
+        for name in names:
+            results.append(
+                self.post_process(
+                    post_process.DoesNotRun, 'final deps.{}'.format(name),
+                )
+            )
+
+        return sum(results[1:], results[0])
diff --git a/recipe_modules/cq_deps/tests/full.expected/abandoned.json b/recipe_modules/cq_deps/tests/full.expected/abandoned.json
new file mode 100644
index 0000000..7737d95
--- /dev/null
+++ b/recipe_modules/cq_deps/tests/full.expected/abandoned.json
@@ -0,0 +1,145 @@
+[
+  {
+    "cmd": [],
+    "name": "resolve CL deps"
+  },
+  {
+    "cmd": [],
+    "name": "resolve CL deps.install infra/tools/luci/gerrit",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "ensure-directory",
+      "--mode",
+      "0777",
+      "[CACHE]/cipd/infra/tools/luci/gerrit/latest"
+    ],
+    "infra_step": true,
+    "name": "resolve CL deps.install infra/tools/luci/gerrit.ensure package directory",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "cipd",
+      "ensure",
+      "-root",
+      "[CACHE]/cipd/infra/tools/luci/gerrit/latest",
+      "-ensure-file",
+      "infra/tools/luci/gerrit/${platform} latest",
+      "-max-threads",
+      "0",
+      "-json-output",
+      "/path/to/tmp/json"
+    ],
+    "infra_step": true,
+    "name": "resolve CL deps.install infra/tools/luci/gerrit.ensure_installed",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@",
+      "@@@STEP_LOG_LINE@json.output@{@@@",
+      "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@    \"\": [@@@",
+      "@@@STEP_LOG_LINE@json.output@      {@@@",
+      "@@@STEP_LOG_LINE@json.output@        \"instance_id\": \"resolved-instance_id-of-latest----------\", @@@",
+      "@@@STEP_LOG_LINE@json.output@        \"package\": \"infra/tools/luci/gerrit/resolved-platform\"@@@",
+      "@@@STEP_LOG_LINE@json.output@      }@@@",
+      "@@@STEP_LOG_LINE@json.output@    ]@@@",
+      "@@@STEP_LOG_LINE@json.output@  }@@@",
+      "@@@STEP_LOG_LINE@json.output@}@@@",
+      "@@@STEP_LOG_END@json.output@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "[CACHE]/cipd/infra/tools/luci/gerrit/latest/gerrit",
+      "change-detail",
+      "-host",
+      "https://pigweed-review.googlesource.com",
+      "-input",
+      "{\"change_id\": \"1\", \"params\": {\"o\": [\"CURRENT_COMMIT\", \"CURRENT_REVISION\"]}}",
+      "-output",
+      "/path/to/tmp/json"
+    ],
+    "name": "resolve CL deps.details pigweed:1",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@",
+      "@@@STEP_LOG_LINE@json.output@{@@@",
+      "@@@STEP_LOG_LINE@json.output@  \"_number\": 1, @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"current_revision\": \"HASH\", @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"project\": \"project\", @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"revisions\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@    \"HASH\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@      \"_number\": 1, @@@",
+      "@@@STEP_LOG_LINE@json.output@      \"commit\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@        \"message\": \"Cq-Depend: pigweed:2\"@@@",
+      "@@@STEP_LOG_LINE@json.output@      }@@@",
+      "@@@STEP_LOG_LINE@json.output@    }@@@",
+      "@@@STEP_LOG_LINE@json.output@  }, @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"status\": \"NEW\"@@@",
+      "@@@STEP_LOG_LINE@json.output@}@@@",
+      "@@@STEP_LOG_END@json.output@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "resolve CL deps.resolve deps for pigweed:1",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@",
+      "@@@STEP_LINK@pigweed:2@https://pigweed-review.googlesource.com/2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "[CACHE]/cipd/infra/tools/luci/gerrit/latest/gerrit",
+      "change-detail",
+      "-host",
+      "https://pigweed-review.googlesource.com",
+      "-input",
+      "{\"change_id\": \"2\", \"params\": {\"o\": [\"CURRENT_COMMIT\", \"CURRENT_REVISION\"]}}",
+      "-output",
+      "/path/to/tmp/json"
+    ],
+    "name": "resolve CL deps.details pigweed:2",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@",
+      "@@@STEP_LOG_LINE@json.output@{@@@",
+      "@@@STEP_LOG_LINE@json.output@  \"_number\": 2, @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"current_revision\": \"HASH\", @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"project\": \"project\", @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"revisions\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@    \"HASH\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@      \"_number\": 1, @@@",
+      "@@@STEP_LOG_LINE@json.output@      \"commit\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@        \"message\": \"Cq-Depend: pigweed:3\"@@@",
+      "@@@STEP_LOG_LINE@json.output@      }@@@",
+      "@@@STEP_LOG_LINE@json.output@    }@@@",
+      "@@@STEP_LOG_LINE@json.output@  }, @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"status\": \"ABANDONED\"@@@",
+      "@@@STEP_LOG_LINE@json.output@}@@@",
+      "@@@STEP_LOG_END@json.output@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "resolve CL deps.pass",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "final deps"
+  },
+  {
+    "name": "$result"
+  }
+]
\ No newline at end of file
diff --git a/recipe_modules/cq_deps/tests/full.expected/bad_field_name.json b/recipe_modules/cq_deps/tests/full.expected/bad_field_name.json
new file mode 100644
index 0000000..10c85b9
--- /dev/null
+++ b/recipe_modules/cq_deps/tests/full.expected/bad_field_name.json
@@ -0,0 +1,113 @@
+[
+  {
+    "cmd": [],
+    "name": "resolve CL deps"
+  },
+  {
+    "cmd": [],
+    "name": "resolve CL deps.install infra/tools/luci/gerrit",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "ensure-directory",
+      "--mode",
+      "0777",
+      "[CACHE]/cipd/infra/tools/luci/gerrit/latest"
+    ],
+    "infra_step": true,
+    "name": "resolve CL deps.install infra/tools/luci/gerrit.ensure package directory",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "cipd",
+      "ensure",
+      "-root",
+      "[CACHE]/cipd/infra/tools/luci/gerrit/latest",
+      "-ensure-file",
+      "infra/tools/luci/gerrit/${platform} latest",
+      "-max-threads",
+      "0",
+      "-json-output",
+      "/path/to/tmp/json"
+    ],
+    "infra_step": true,
+    "name": "resolve CL deps.install infra/tools/luci/gerrit.ensure_installed",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@",
+      "@@@STEP_LOG_LINE@json.output@{@@@",
+      "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@    \"\": [@@@",
+      "@@@STEP_LOG_LINE@json.output@      {@@@",
+      "@@@STEP_LOG_LINE@json.output@        \"instance_id\": \"resolved-instance_id-of-latest----------\", @@@",
+      "@@@STEP_LOG_LINE@json.output@        \"package\": \"infra/tools/luci/gerrit/resolved-platform\"@@@",
+      "@@@STEP_LOG_LINE@json.output@      }@@@",
+      "@@@STEP_LOG_LINE@json.output@    ]@@@",
+      "@@@STEP_LOG_LINE@json.output@  }@@@",
+      "@@@STEP_LOG_LINE@json.output@}@@@",
+      "@@@STEP_LOG_END@json.output@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "[CACHE]/cipd/infra/tools/luci/gerrit/latest/gerrit",
+      "change-detail",
+      "-host",
+      "https://pigweed-review.googlesource.com",
+      "-input",
+      "{\"change_id\": \"1\", \"params\": {\"o\": [\"CURRENT_COMMIT\", \"CURRENT_REVISION\"]}}",
+      "-output",
+      "/path/to/tmp/json"
+    ],
+    "name": "resolve CL deps.details pigweed:1",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@",
+      "@@@STEP_LOG_LINE@json.output@{@@@",
+      "@@@STEP_LOG_LINE@json.output@  \"_number\": 1, @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"current_revision\": \"HASH\", @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"project\": \"project\", @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"revisions\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@    \"HASH\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@      \"_number\": 1, @@@",
+      "@@@STEP_LOG_LINE@json.output@      \"commit\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@        \"message\": \"Depends: pigweed:2\"@@@",
+      "@@@STEP_LOG_LINE@json.output@      }@@@",
+      "@@@STEP_LOG_LINE@json.output@    }@@@",
+      "@@@STEP_LOG_LINE@json.output@  }, @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"status\": \"NEW\"@@@",
+      "@@@STEP_LOG_LINE@json.output@}@@@",
+      "@@@STEP_LOG_END@json.output@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "resolve CL deps.resolve deps for pigweed:1",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "resolve CL deps.pass",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "final deps"
+  },
+  {
+    "name": "$result"
+  }
+]
\ No newline at end of file
diff --git a/recipe_modules/cq_deps/tests/full.expected/commas.json b/recipe_modules/cq_deps/tests/full.expected/commas.json
new file mode 100644
index 0000000..aadcfc4
--- /dev/null
+++ b/recipe_modules/cq_deps/tests/full.expected/commas.json
@@ -0,0 +1,161 @@
+[
+  {
+    "cmd": [],
+    "name": "resolve CL deps",
+    "~followup_annotations": [
+      "@@@STEP_LINK@pigweed:2@https://pigweed-review.googlesource.com/2@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "resolve CL deps.install infra/tools/luci/gerrit",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "ensure-directory",
+      "--mode",
+      "0777",
+      "[CACHE]/cipd/infra/tools/luci/gerrit/latest"
+    ],
+    "infra_step": true,
+    "name": "resolve CL deps.install infra/tools/luci/gerrit.ensure package directory",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "cipd",
+      "ensure",
+      "-root",
+      "[CACHE]/cipd/infra/tools/luci/gerrit/latest",
+      "-ensure-file",
+      "infra/tools/luci/gerrit/${platform} latest",
+      "-max-threads",
+      "0",
+      "-json-output",
+      "/path/to/tmp/json"
+    ],
+    "infra_step": true,
+    "name": "resolve CL deps.install infra/tools/luci/gerrit.ensure_installed",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@",
+      "@@@STEP_LOG_LINE@json.output@{@@@",
+      "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@    \"\": [@@@",
+      "@@@STEP_LOG_LINE@json.output@      {@@@",
+      "@@@STEP_LOG_LINE@json.output@        \"instance_id\": \"resolved-instance_id-of-latest----------\", @@@",
+      "@@@STEP_LOG_LINE@json.output@        \"package\": \"infra/tools/luci/gerrit/resolved-platform\"@@@",
+      "@@@STEP_LOG_LINE@json.output@      }@@@",
+      "@@@STEP_LOG_LINE@json.output@    ]@@@",
+      "@@@STEP_LOG_LINE@json.output@  }@@@",
+      "@@@STEP_LOG_LINE@json.output@}@@@",
+      "@@@STEP_LOG_END@json.output@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "[CACHE]/cipd/infra/tools/luci/gerrit/latest/gerrit",
+      "change-detail",
+      "-host",
+      "https://pigweed-review.googlesource.com",
+      "-input",
+      "{\"change_id\": \"1\", \"params\": {\"o\": [\"CURRENT_COMMIT\", \"CURRENT_REVISION\"]}}",
+      "-output",
+      "/path/to/tmp/json"
+    ],
+    "name": "resolve CL deps.details pigweed:1",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@",
+      "@@@STEP_LOG_LINE@json.output@{@@@",
+      "@@@STEP_LOG_LINE@json.output@  \"_number\": 1, @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"current_revision\": \"HASH\", @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"project\": \"project\", @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"revisions\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@    \"HASH\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@      \"_number\": 1, @@@",
+      "@@@STEP_LOG_LINE@json.output@      \"commit\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@        \"message\": \"Cq-Depend: ,,pigweed:2,\"@@@",
+      "@@@STEP_LOG_LINE@json.output@      }@@@",
+      "@@@STEP_LOG_LINE@json.output@    }@@@",
+      "@@@STEP_LOG_LINE@json.output@  }, @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"status\": \"NEW\"@@@",
+      "@@@STEP_LOG_LINE@json.output@}@@@",
+      "@@@STEP_LOG_END@json.output@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "resolve CL deps.resolve deps for pigweed:1",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@",
+      "@@@STEP_LINK@pigweed:2@https://pigweed-review.googlesource.com/2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "[CACHE]/cipd/infra/tools/luci/gerrit/latest/gerrit",
+      "change-detail",
+      "-host",
+      "https://pigweed-review.googlesource.com",
+      "-input",
+      "{\"change_id\": \"2\", \"params\": {\"o\": [\"CURRENT_COMMIT\", \"CURRENT_REVISION\"]}}",
+      "-output",
+      "/path/to/tmp/json"
+    ],
+    "name": "resolve CL deps.details pigweed:2",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@",
+      "@@@STEP_LOG_LINE@json.output@{@@@",
+      "@@@STEP_LOG_LINE@json.output@  \"current_revision\": \"HASH\", @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"project\": \"project\", @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"revisions\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@    \"HASH\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@      \"_number\": 1, @@@",
+      "@@@STEP_LOG_LINE@json.output@      \"commit\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@        \"message\": \"\"@@@",
+      "@@@STEP_LOG_LINE@json.output@      }@@@",
+      "@@@STEP_LOG_LINE@json.output@    }@@@",
+      "@@@STEP_LOG_LINE@json.output@  }, @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"status\": \"NEW\"@@@",
+      "@@@STEP_LOG_LINE@json.output@}@@@",
+      "@@@STEP_LOG_END@json.output@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "resolve CL deps.resolve deps for pigweed:2",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "resolve CL deps.pass",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "final deps"
+  },
+  {
+    "cmd": [],
+    "name": "final deps.pigweed:2",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "name": "$result"
+  }
+]
\ No newline at end of file
diff --git a/recipe_modules/cq_deps/tests/full.expected/error.json b/recipe_modules/cq_deps/tests/full.expected/error.json
new file mode 100644
index 0000000..25f0220
--- /dev/null
+++ b/recipe_modules/cq_deps/tests/full.expected/error.json
@@ -0,0 +1,230 @@
+[
+  {
+    "cmd": [],
+    "name": "resolve CL deps"
+  },
+  {
+    "cmd": [],
+    "name": "resolve CL deps.install infra/tools/luci/gerrit",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "ensure-directory",
+      "--mode",
+      "0777",
+      "[CACHE]/cipd/infra/tools/luci/gerrit/latest"
+    ],
+    "infra_step": true,
+    "name": "resolve CL deps.install infra/tools/luci/gerrit.ensure package directory",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "cipd",
+      "ensure",
+      "-root",
+      "[CACHE]/cipd/infra/tools/luci/gerrit/latest",
+      "-ensure-file",
+      "infra/tools/luci/gerrit/${platform} latest",
+      "-max-threads",
+      "0",
+      "-json-output",
+      "/path/to/tmp/json"
+    ],
+    "infra_step": true,
+    "name": "resolve CL deps.install infra/tools/luci/gerrit.ensure_installed",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@",
+      "@@@STEP_LOG_LINE@json.output@{@@@",
+      "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@    \"\": [@@@",
+      "@@@STEP_LOG_LINE@json.output@      {@@@",
+      "@@@STEP_LOG_LINE@json.output@        \"instance_id\": \"resolved-instance_id-of-latest----------\", @@@",
+      "@@@STEP_LOG_LINE@json.output@        \"package\": \"infra/tools/luci/gerrit/resolved-platform\"@@@",
+      "@@@STEP_LOG_LINE@json.output@      }@@@",
+      "@@@STEP_LOG_LINE@json.output@    ]@@@",
+      "@@@STEP_LOG_LINE@json.output@  }@@@",
+      "@@@STEP_LOG_LINE@json.output@}@@@",
+      "@@@STEP_LOG_END@json.output@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "[CACHE]/cipd/infra/tools/luci/gerrit/latest/gerrit",
+      "change-detail",
+      "-host",
+      "https://pigweed-review.googlesource.com",
+      "-input",
+      "{\"change_id\": \"1\", \"params\": {\"o\": [\"CURRENT_COMMIT\", \"CURRENT_REVISION\"]}}",
+      "-output",
+      "/path/to/tmp/json"
+    ],
+    "name": "resolve CL deps.details pigweed:1",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@",
+      "@@@STEP_LOG_LINE@json.output@{@@@",
+      "@@@STEP_LOG_LINE@json.output@  \"_number\": 1, @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"current_revision\": \"HASH\", @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"project\": \"project\", @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"revisions\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@    \"HASH\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@      \"_number\": 1, @@@",
+      "@@@STEP_LOG_LINE@json.output@      \"commit\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@        \"message\": \"Cq-Depend: pigweed:2\"@@@",
+      "@@@STEP_LOG_LINE@json.output@      }@@@",
+      "@@@STEP_LOG_LINE@json.output@    }@@@",
+      "@@@STEP_LOG_LINE@json.output@  }, @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"status\": \"NEW\"@@@",
+      "@@@STEP_LOG_LINE@json.output@}@@@",
+      "@@@STEP_LOG_END@json.output@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "resolve CL deps.resolve deps for pigweed:1",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@",
+      "@@@STEP_LINK@pigweed:2@https://pigweed-review.googlesource.com/2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "[CACHE]/cipd/infra/tools/luci/gerrit/latest/gerrit",
+      "change-detail",
+      "-host",
+      "https://pigweed-review.googlesource.com",
+      "-input",
+      "{\"change_id\": \"2\", \"params\": {\"o\": [\"CURRENT_COMMIT\", \"CURRENT_REVISION\"]}}",
+      "-output",
+      "/path/to/tmp/json"
+    ],
+    "name": "resolve CL deps.details pigweed:2",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@",
+      "@@@STEP_LOG_LINE@json.output@{@@@",
+      "@@@STEP_LOG_LINE@json.output@  \"transient\": true@@@",
+      "@@@STEP_LOG_LINE@json.output@}@@@",
+      "@@@STEP_LOG_END@json.output@@@",
+      "@@@STEP_FAILURE@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "[CACHE]/cipd/infra/tools/luci/gerrit/latest/gerrit",
+      "change-detail",
+      "-host",
+      "https://pigweed-review.googlesource.com",
+      "-input",
+      "{\"change_id\": \"2\", \"params\": {\"o\": [\"CURRENT_COMMIT\", \"CURRENT_REVISION\"]}}",
+      "-output",
+      "/path/to/tmp/json"
+    ],
+    "name": "resolve CL deps.details pigweed:2 (2)",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@",
+      "@@@STEP_LOG_LINE@json.output@{@@@",
+      "@@@STEP_LOG_LINE@json.output@  \"transient\": true@@@",
+      "@@@STEP_LOG_LINE@json.output@}@@@",
+      "@@@STEP_LOG_END@json.output@@@",
+      "@@@STEP_FAILURE@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "[CACHE]/cipd/infra/tools/luci/gerrit/latest/gerrit",
+      "change-detail",
+      "-host",
+      "https://pigweed-review.googlesource.com",
+      "-input",
+      "{\"change_id\": \"2\", \"params\": {\"o\": [\"CURRENT_COMMIT\", \"CURRENT_REVISION\"]}}",
+      "-output",
+      "/path/to/tmp/json"
+    ],
+    "name": "resolve CL deps.details pigweed:2 (3)",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@",
+      "@@@STEP_LOG_LINE@json.output@{@@@",
+      "@@@STEP_LOG_LINE@json.output@  \"transient\": true@@@",
+      "@@@STEP_LOG_LINE@json.output@}@@@",
+      "@@@STEP_LOG_END@json.output@@@",
+      "@@@STEP_FAILURE@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "[CACHE]/cipd/infra/tools/luci/gerrit/latest/gerrit",
+      "change-detail",
+      "-host",
+      "https://pigweed-review.googlesource.com",
+      "-input",
+      "{\"change_id\": \"2\", \"params\": {\"o\": [\"CURRENT_COMMIT\", \"CURRENT_REVISION\"]}}",
+      "-output",
+      "/path/to/tmp/json"
+    ],
+    "name": "resolve CL deps.details pigweed:2 (4)",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@",
+      "@@@STEP_LOG_LINE@json.output@{@@@",
+      "@@@STEP_LOG_LINE@json.output@  \"transient\": true@@@",
+      "@@@STEP_LOG_LINE@json.output@}@@@",
+      "@@@STEP_LOG_END@json.output@@@",
+      "@@@STEP_FAILURE@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "[CACHE]/cipd/infra/tools/luci/gerrit/latest/gerrit",
+      "change-detail",
+      "-host",
+      "https://pigweed-review.googlesource.com",
+      "-input",
+      "{\"change_id\": \"2\", \"params\": {\"o\": [\"CURRENT_COMMIT\", \"CURRENT_REVISION\"]}}",
+      "-output",
+      "/path/to/tmp/json"
+    ],
+    "name": "resolve CL deps.details pigweed:2 (5)",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@",
+      "@@@STEP_LOG_LINE@json.output@{@@@",
+      "@@@STEP_LOG_LINE@json.output@  \"transient\": true@@@",
+      "@@@STEP_LOG_LINE@json.output@}@@@",
+      "@@@STEP_LOG_END@json.output@@@",
+      "@@@STEP_FAILURE@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "resolve CL deps.pass",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "final deps"
+  },
+  {
+    "cmd": [],
+    "name": "errors"
+  },
+  {
+    "cmd": [],
+    "name": "errors.pigweed:2",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "name": "$result"
+  }
+]
\ No newline at end of file
diff --git a/recipe_modules/cq_deps/tests/full.expected/error_recovery.json b/recipe_modules/cq_deps/tests/full.expected/error_recovery.json
new file mode 100644
index 0000000..c588b32
--- /dev/null
+++ b/recipe_modules/cq_deps/tests/full.expected/error_recovery.json
@@ -0,0 +1,225 @@
+[
+  {
+    "cmd": [],
+    "name": "resolve CL deps",
+    "~followup_annotations": [
+      "@@@STEP_LINK@pigweed:2@https://pigweed-review.googlesource.com/2@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "resolve CL deps.install infra/tools/luci/gerrit",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "ensure-directory",
+      "--mode",
+      "0777",
+      "[CACHE]/cipd/infra/tools/luci/gerrit/latest"
+    ],
+    "infra_step": true,
+    "name": "resolve CL deps.install infra/tools/luci/gerrit.ensure package directory",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "cipd",
+      "ensure",
+      "-root",
+      "[CACHE]/cipd/infra/tools/luci/gerrit/latest",
+      "-ensure-file",
+      "infra/tools/luci/gerrit/${platform} latest",
+      "-max-threads",
+      "0",
+      "-json-output",
+      "/path/to/tmp/json"
+    ],
+    "infra_step": true,
+    "name": "resolve CL deps.install infra/tools/luci/gerrit.ensure_installed",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@",
+      "@@@STEP_LOG_LINE@json.output@{@@@",
+      "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@    \"\": [@@@",
+      "@@@STEP_LOG_LINE@json.output@      {@@@",
+      "@@@STEP_LOG_LINE@json.output@        \"instance_id\": \"resolved-instance_id-of-latest----------\", @@@",
+      "@@@STEP_LOG_LINE@json.output@        \"package\": \"infra/tools/luci/gerrit/resolved-platform\"@@@",
+      "@@@STEP_LOG_LINE@json.output@      }@@@",
+      "@@@STEP_LOG_LINE@json.output@    ]@@@",
+      "@@@STEP_LOG_LINE@json.output@  }@@@",
+      "@@@STEP_LOG_LINE@json.output@}@@@",
+      "@@@STEP_LOG_END@json.output@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "[CACHE]/cipd/infra/tools/luci/gerrit/latest/gerrit",
+      "change-detail",
+      "-host",
+      "https://pigweed-review.googlesource.com",
+      "-input",
+      "{\"change_id\": \"1\", \"params\": {\"o\": [\"CURRENT_COMMIT\", \"CURRENT_REVISION\"]}}",
+      "-output",
+      "/path/to/tmp/json"
+    ],
+    "name": "resolve CL deps.details pigweed:1",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@",
+      "@@@STEP_LOG_LINE@json.output@{@@@",
+      "@@@STEP_LOG_LINE@json.output@  \"_number\": 1, @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"current_revision\": \"HASH\", @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"project\": \"project\", @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"revisions\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@    \"HASH\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@      \"_number\": 1, @@@",
+      "@@@STEP_LOG_LINE@json.output@      \"commit\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@        \"message\": \"Cq-Depend: pigweed:2\"@@@",
+      "@@@STEP_LOG_LINE@json.output@      }@@@",
+      "@@@STEP_LOG_LINE@json.output@    }@@@",
+      "@@@STEP_LOG_LINE@json.output@  }, @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"status\": \"NEW\"@@@",
+      "@@@STEP_LOG_LINE@json.output@}@@@",
+      "@@@STEP_LOG_END@json.output@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "resolve CL deps.resolve deps for pigweed:1",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@",
+      "@@@STEP_LINK@pigweed:2@https://pigweed-review.googlesource.com/2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "[CACHE]/cipd/infra/tools/luci/gerrit/latest/gerrit",
+      "change-detail",
+      "-host",
+      "https://pigweed-review.googlesource.com",
+      "-input",
+      "{\"change_id\": \"2\", \"params\": {\"o\": [\"CURRENT_COMMIT\", \"CURRENT_REVISION\"]}}",
+      "-output",
+      "/path/to/tmp/json"
+    ],
+    "name": "resolve CL deps.details pigweed:2",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@",
+      "@@@STEP_LOG_LINE@json.output@{@@@",
+      "@@@STEP_LOG_LINE@json.output@  \"transient\": true@@@",
+      "@@@STEP_LOG_LINE@json.output@}@@@",
+      "@@@STEP_LOG_END@json.output@@@",
+      "@@@STEP_FAILURE@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "[CACHE]/cipd/infra/tools/luci/gerrit/latest/gerrit",
+      "change-detail",
+      "-host",
+      "https://pigweed-review.googlesource.com",
+      "-input",
+      "{\"change_id\": \"2\", \"params\": {\"o\": [\"CURRENT_COMMIT\", \"CURRENT_REVISION\"]}}",
+      "-output",
+      "/path/to/tmp/json"
+    ],
+    "name": "resolve CL deps.details pigweed:2 (2)",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@",
+      "@@@STEP_LOG_LINE@json.output@{@@@",
+      "@@@STEP_LOG_LINE@json.output@  \"transient\": true@@@",
+      "@@@STEP_LOG_LINE@json.output@}@@@",
+      "@@@STEP_LOG_END@json.output@@@",
+      "@@@STEP_FAILURE@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "[CACHE]/cipd/infra/tools/luci/gerrit/latest/gerrit",
+      "change-detail",
+      "-host",
+      "https://pigweed-review.googlesource.com",
+      "-input",
+      "{\"change_id\": \"2\", \"params\": {\"o\": [\"CURRENT_COMMIT\", \"CURRENT_REVISION\"]}}",
+      "-output",
+      "/path/to/tmp/json"
+    ],
+    "name": "resolve CL deps.details pigweed:2 (3)",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@",
+      "@@@STEP_LOG_LINE@json.output@{@@@",
+      "@@@STEP_LOG_LINE@json.output@  \"transient\": true@@@",
+      "@@@STEP_LOG_LINE@json.output@}@@@",
+      "@@@STEP_LOG_END@json.output@@@",
+      "@@@STEP_FAILURE@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "[CACHE]/cipd/infra/tools/luci/gerrit/latest/gerrit",
+      "change-detail",
+      "-host",
+      "https://pigweed-review.googlesource.com",
+      "-input",
+      "{\"change_id\": \"2\", \"params\": {\"o\": [\"CURRENT_COMMIT\", \"CURRENT_REVISION\"]}}",
+      "-output",
+      "/path/to/tmp/json"
+    ],
+    "name": "resolve CL deps.details pigweed:2 (4)",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@",
+      "@@@STEP_LOG_LINE@json.output@{@@@",
+      "@@@STEP_LOG_LINE@json.output@  \"_number\": 2, @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"current_revision\": \"HASH\", @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"project\": \"project\", @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"revisions\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@    \"HASH\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@      \"_number\": 1, @@@",
+      "@@@STEP_LOG_LINE@json.output@      \"commit\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@        \"message\": \"\"@@@",
+      "@@@STEP_LOG_LINE@json.output@      }@@@",
+      "@@@STEP_LOG_LINE@json.output@    }@@@",
+      "@@@STEP_LOG_LINE@json.output@  }, @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"status\": \"NEW\"@@@",
+      "@@@STEP_LOG_LINE@json.output@}@@@",
+      "@@@STEP_LOG_END@json.output@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "resolve CL deps.resolve deps for pigweed:2",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "resolve CL deps.pass",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "final deps"
+  },
+  {
+    "cmd": [],
+    "name": "final deps.pigweed:2",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "name": "$result"
+  }
+]
\ No newline at end of file
diff --git a/recipe_modules/cq_deps/tests/full.expected/forbidden.json b/recipe_modules/cq_deps/tests/full.expected/forbidden.json
new file mode 100644
index 0000000..99e845f
--- /dev/null
+++ b/recipe_modules/cq_deps/tests/full.expected/forbidden.json
@@ -0,0 +1,144 @@
+[
+  {
+    "cmd": [],
+    "name": "resolve CL deps"
+  },
+  {
+    "cmd": [],
+    "name": "resolve CL deps.install infra/tools/luci/gerrit",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "ensure-directory",
+      "--mode",
+      "0777",
+      "[CACHE]/cipd/infra/tools/luci/gerrit/latest"
+    ],
+    "infra_step": true,
+    "name": "resolve CL deps.install infra/tools/luci/gerrit.ensure package directory",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "cipd",
+      "ensure",
+      "-root",
+      "[CACHE]/cipd/infra/tools/luci/gerrit/latest",
+      "-ensure-file",
+      "infra/tools/luci/gerrit/${platform} latest",
+      "-max-threads",
+      "0",
+      "-json-output",
+      "/path/to/tmp/json"
+    ],
+    "infra_step": true,
+    "name": "resolve CL deps.install infra/tools/luci/gerrit.ensure_installed",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@",
+      "@@@STEP_LOG_LINE@json.output@{@@@",
+      "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@    \"\": [@@@",
+      "@@@STEP_LOG_LINE@json.output@      {@@@",
+      "@@@STEP_LOG_LINE@json.output@        \"instance_id\": \"resolved-instance_id-of-latest----------\", @@@",
+      "@@@STEP_LOG_LINE@json.output@        \"package\": \"infra/tools/luci/gerrit/resolved-platform\"@@@",
+      "@@@STEP_LOG_LINE@json.output@      }@@@",
+      "@@@STEP_LOG_LINE@json.output@    ]@@@",
+      "@@@STEP_LOG_LINE@json.output@  }@@@",
+      "@@@STEP_LOG_LINE@json.output@}@@@",
+      "@@@STEP_LOG_END@json.output@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "[CACHE]/cipd/infra/tools/luci/gerrit/latest/gerrit",
+      "change-detail",
+      "-host",
+      "https://pigweed-review.googlesource.com",
+      "-input",
+      "{\"change_id\": \"1\", \"params\": {\"o\": [\"CURRENT_COMMIT\", \"CURRENT_REVISION\"]}}",
+      "-output",
+      "/path/to/tmp/json"
+    ],
+    "name": "resolve CL deps.details pigweed:1",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@",
+      "@@@STEP_LOG_LINE@json.output@{@@@",
+      "@@@STEP_LOG_LINE@json.output@  \"_number\": 1, @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"current_revision\": \"HASH\", @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"project\": \"project\", @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"revisions\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@    \"HASH\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@      \"_number\": 1, @@@",
+      "@@@STEP_LOG_LINE@json.output@      \"commit\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@        \"message\": \"Cq-Depend: forbidden:1\"@@@",
+      "@@@STEP_LOG_LINE@json.output@      }@@@",
+      "@@@STEP_LOG_LINE@json.output@    }@@@",
+      "@@@STEP_LOG_LINE@json.output@  }, @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"status\": \"NEW\"@@@",
+      "@@@STEP_LOG_LINE@json.output@}@@@",
+      "@@@STEP_LOG_END@json.output@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "resolve CL deps.resolve deps for pigweed:1",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@",
+      "@@@STEP_LINK@forbidden:1@https://forbidden-review.googlesource.com/1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "[CACHE]/cipd/infra/tools/luci/gerrit/latest/gerrit",
+      "change-detail",
+      "-host",
+      "https://forbidden-review.googlesource.com",
+      "-input",
+      "{\"change_id\": \"1\", \"params\": {\"o\": [\"CURRENT_COMMIT\", \"CURRENT_REVISION\"]}}",
+      "-output",
+      "/path/to/tmp/json"
+    ],
+    "name": "resolve CL deps.details forbidden:1",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@",
+      "@@@STEP_LOG_LINE@json.output@{}@@@",
+      "@@@STEP_LOG_END@json.output@@@",
+      "@@@STEP_FAILURE@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "resolve CL deps.pass",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "final deps"
+  },
+  {
+    "cmd": [],
+    "name": "errors"
+  },
+  {
+    "cmd": [],
+    "name": "errors.forbidden:1",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "name": "$result"
+  }
+]
\ No newline at end of file
diff --git a/recipe_modules/cq_deps/tests/full.expected/hash.json b/recipe_modules/cq_deps/tests/full.expected/hash.json
new file mode 100644
index 0000000..ed2003f
--- /dev/null
+++ b/recipe_modules/cq_deps/tests/full.expected/hash.json
@@ -0,0 +1,135 @@
+[
+  {
+    "cmd": [],
+    "name": "resolve CL deps"
+  },
+  {
+    "cmd": [],
+    "name": "resolve CL deps.install infra/tools/luci/gerrit",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "ensure-directory",
+      "--mode",
+      "0777",
+      "[CACHE]/cipd/infra/tools/luci/gerrit/latest"
+    ],
+    "infra_step": true,
+    "name": "resolve CL deps.install infra/tools/luci/gerrit.ensure package directory",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "cipd",
+      "ensure",
+      "-root",
+      "[CACHE]/cipd/infra/tools/luci/gerrit/latest",
+      "-ensure-file",
+      "infra/tools/luci/gerrit/${platform} latest",
+      "-max-threads",
+      "0",
+      "-json-output",
+      "/path/to/tmp/json"
+    ],
+    "infra_step": true,
+    "name": "resolve CL deps.install infra/tools/luci/gerrit.ensure_installed",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@",
+      "@@@STEP_LOG_LINE@json.output@{@@@",
+      "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@    \"\": [@@@",
+      "@@@STEP_LOG_LINE@json.output@      {@@@",
+      "@@@STEP_LOG_LINE@json.output@        \"instance_id\": \"resolved-instance_id-of-latest----------\", @@@",
+      "@@@STEP_LOG_LINE@json.output@        \"package\": \"infra/tools/luci/gerrit/resolved-platform\"@@@",
+      "@@@STEP_LOG_LINE@json.output@      }@@@",
+      "@@@STEP_LOG_LINE@json.output@    ]@@@",
+      "@@@STEP_LOG_LINE@json.output@  }@@@",
+      "@@@STEP_LOG_LINE@json.output@}@@@",
+      "@@@STEP_LOG_END@json.output@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "[CACHE]/cipd/infra/tools/luci/gerrit/latest/gerrit",
+      "change-query",
+      "-host",
+      "https://pigweed-review.googlesource.com",
+      "-input",
+      "{\"params\": {\"q\": {\"commit\": \"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\"}}}",
+      "-output",
+      "/path/to/tmp/json"
+    ],
+    "name": "resolve CL deps.number",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@",
+      "@@@STEP_LOG_LINE@json.output@[@@@",
+      "@@@STEP_LOG_LINE@json.output@  {@@@",
+      "@@@STEP_LOG_LINE@json.output@    \"_number\": 1@@@",
+      "@@@STEP_LOG_LINE@json.output@  }@@@",
+      "@@@STEP_LOG_LINE@json.output@]@@@",
+      "@@@STEP_LOG_END@json.output@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "[CACHE]/cipd/infra/tools/luci/gerrit/latest/gerrit",
+      "change-detail",
+      "-host",
+      "https://pigweed-review.googlesource.com",
+      "-input",
+      "{\"change_id\": \"1\", \"params\": {\"o\": [\"CURRENT_COMMIT\", \"CURRENT_REVISION\"]}}",
+      "-output",
+      "/path/to/tmp/json"
+    ],
+    "name": "resolve CL deps.details pigweed:1",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@",
+      "@@@STEP_LOG_LINE@json.output@{@@@",
+      "@@@STEP_LOG_LINE@json.output@  \"_number\": 1, @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"current_revision\": \"HASH\", @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"project\": \"project\", @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"revisions\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@    \"HASH\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@      \"_number\": 1, @@@",
+      "@@@STEP_LOG_LINE@json.output@      \"commit\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@        \"message\": \"\"@@@",
+      "@@@STEP_LOG_LINE@json.output@      }@@@",
+      "@@@STEP_LOG_LINE@json.output@    }@@@",
+      "@@@STEP_LOG_LINE@json.output@  }, @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"status\": \"NEW\"@@@",
+      "@@@STEP_LOG_LINE@json.output@}@@@",
+      "@@@STEP_LOG_END@json.output@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "resolve CL deps.resolve deps for pigweed:1",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "resolve CL deps.pass",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "final deps"
+  },
+  {
+    "name": "$result"
+  }
+]
\ No newline at end of file
diff --git a/recipe_modules/cq_deps/tests/full.expected/loop_three.json b/recipe_modules/cq_deps/tests/full.expected/loop_three.json
new file mode 100644
index 0000000..bab90c5
--- /dev/null
+++ b/recipe_modules/cq_deps/tests/full.expected/loop_three.json
@@ -0,0 +1,210 @@
+[
+  {
+    "cmd": [],
+    "name": "resolve CL deps",
+    "~followup_annotations": [
+      "@@@STEP_LINK@pigweed:2@https://pigweed-review.googlesource.com/2@@@",
+      "@@@STEP_LINK@pigweed:3@https://pigweed-review.googlesource.com/3@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "resolve CL deps.install infra/tools/luci/gerrit",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "ensure-directory",
+      "--mode",
+      "0777",
+      "[CACHE]/cipd/infra/tools/luci/gerrit/latest"
+    ],
+    "infra_step": true,
+    "name": "resolve CL deps.install infra/tools/luci/gerrit.ensure package directory",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "cipd",
+      "ensure",
+      "-root",
+      "[CACHE]/cipd/infra/tools/luci/gerrit/latest",
+      "-ensure-file",
+      "infra/tools/luci/gerrit/${platform} latest",
+      "-max-threads",
+      "0",
+      "-json-output",
+      "/path/to/tmp/json"
+    ],
+    "infra_step": true,
+    "name": "resolve CL deps.install infra/tools/luci/gerrit.ensure_installed",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@",
+      "@@@STEP_LOG_LINE@json.output@{@@@",
+      "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@    \"\": [@@@",
+      "@@@STEP_LOG_LINE@json.output@      {@@@",
+      "@@@STEP_LOG_LINE@json.output@        \"instance_id\": \"resolved-instance_id-of-latest----------\", @@@",
+      "@@@STEP_LOG_LINE@json.output@        \"package\": \"infra/tools/luci/gerrit/resolved-platform\"@@@",
+      "@@@STEP_LOG_LINE@json.output@      }@@@",
+      "@@@STEP_LOG_LINE@json.output@    ]@@@",
+      "@@@STEP_LOG_LINE@json.output@  }@@@",
+      "@@@STEP_LOG_LINE@json.output@}@@@",
+      "@@@STEP_LOG_END@json.output@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "[CACHE]/cipd/infra/tools/luci/gerrit/latest/gerrit",
+      "change-detail",
+      "-host",
+      "https://pigweed-review.googlesource.com",
+      "-input",
+      "{\"change_id\": \"1\", \"params\": {\"o\": [\"CURRENT_COMMIT\", \"CURRENT_REVISION\"]}}",
+      "-output",
+      "/path/to/tmp/json"
+    ],
+    "name": "resolve CL deps.details pigweed:1",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@",
+      "@@@STEP_LOG_LINE@json.output@{@@@",
+      "@@@STEP_LOG_LINE@json.output@  \"_number\": 1, @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"current_revision\": \"HASH\", @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"project\": \"project\", @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"revisions\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@    \"HASH\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@      \"_number\": 1, @@@",
+      "@@@STEP_LOG_LINE@json.output@      \"commit\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@        \"message\": \"Cq-Depend: pigweed:2\"@@@",
+      "@@@STEP_LOG_LINE@json.output@      }@@@",
+      "@@@STEP_LOG_LINE@json.output@    }@@@",
+      "@@@STEP_LOG_LINE@json.output@  }, @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"status\": \"NEW\"@@@",
+      "@@@STEP_LOG_LINE@json.output@}@@@",
+      "@@@STEP_LOG_END@json.output@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "resolve CL deps.resolve deps for pigweed:1",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@",
+      "@@@STEP_LINK@pigweed:2@https://pigweed-review.googlesource.com/2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "[CACHE]/cipd/infra/tools/luci/gerrit/latest/gerrit",
+      "change-detail",
+      "-host",
+      "https://pigweed-review.googlesource.com",
+      "-input",
+      "{\"change_id\": \"2\", \"params\": {\"o\": [\"CURRENT_COMMIT\", \"CURRENT_REVISION\"]}}",
+      "-output",
+      "/path/to/tmp/json"
+    ],
+    "name": "resolve CL deps.details pigweed:2",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@",
+      "@@@STEP_LOG_LINE@json.output@{@@@",
+      "@@@STEP_LOG_LINE@json.output@  \"_number\": 2, @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"current_revision\": \"HASH\", @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"project\": \"project\", @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"revisions\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@    \"HASH\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@      \"_number\": 1, @@@",
+      "@@@STEP_LOG_LINE@json.output@      \"commit\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@        \"message\": \"Cq-Depend: pigweed:3\"@@@",
+      "@@@STEP_LOG_LINE@json.output@      }@@@",
+      "@@@STEP_LOG_LINE@json.output@    }@@@",
+      "@@@STEP_LOG_LINE@json.output@  }, @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"status\": \"NEW\"@@@",
+      "@@@STEP_LOG_LINE@json.output@}@@@",
+      "@@@STEP_LOG_END@json.output@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "resolve CL deps.resolve deps for pigweed:2",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@",
+      "@@@STEP_LINK@pigweed:3@https://pigweed-review.googlesource.com/3@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "[CACHE]/cipd/infra/tools/luci/gerrit/latest/gerrit",
+      "change-detail",
+      "-host",
+      "https://pigweed-review.googlesource.com",
+      "-input",
+      "{\"change_id\": \"3\", \"params\": {\"o\": [\"CURRENT_COMMIT\", \"CURRENT_REVISION\"]}}",
+      "-output",
+      "/path/to/tmp/json"
+    ],
+    "name": "resolve CL deps.details pigweed:3",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@",
+      "@@@STEP_LOG_LINE@json.output@{@@@",
+      "@@@STEP_LOG_LINE@json.output@  \"_number\": 3, @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"current_revision\": \"HASH\", @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"project\": \"project\", @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"revisions\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@    \"HASH\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@      \"_number\": 1, @@@",
+      "@@@STEP_LOG_LINE@json.output@      \"commit\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@        \"message\": \"Cq-Depend: pigweed:1\"@@@",
+      "@@@STEP_LOG_LINE@json.output@      }@@@",
+      "@@@STEP_LOG_LINE@json.output@    }@@@",
+      "@@@STEP_LOG_LINE@json.output@  }, @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"status\": \"NEW\"@@@",
+      "@@@STEP_LOG_LINE@json.output@}@@@",
+      "@@@STEP_LOG_END@json.output@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "resolve CL deps.resolve deps for pigweed:3",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@",
+      "@@@STEP_LINK@pigweed:1@https://pigweed-review.googlesource.com/1@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "resolve CL deps.pass",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "final deps"
+  },
+  {
+    "cmd": [],
+    "name": "final deps.pigweed:2",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "final deps.pigweed:3",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "name": "$result"
+  }
+]
\ No newline at end of file
diff --git a/recipe_modules/cq_deps/tests/full.expected/loop_two.json b/recipe_modules/cq_deps/tests/full.expected/loop_two.json
new file mode 100644
index 0000000..4594116
--- /dev/null
+++ b/recipe_modules/cq_deps/tests/full.expected/loop_two.json
@@ -0,0 +1,163 @@
+[
+  {
+    "cmd": [],
+    "name": "resolve CL deps",
+    "~followup_annotations": [
+      "@@@STEP_LINK@pigweed:2@https://pigweed-review.googlesource.com/2@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "resolve CL deps.install infra/tools/luci/gerrit",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "ensure-directory",
+      "--mode",
+      "0777",
+      "[CACHE]/cipd/infra/tools/luci/gerrit/latest"
+    ],
+    "infra_step": true,
+    "name": "resolve CL deps.install infra/tools/luci/gerrit.ensure package directory",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "cipd",
+      "ensure",
+      "-root",
+      "[CACHE]/cipd/infra/tools/luci/gerrit/latest",
+      "-ensure-file",
+      "infra/tools/luci/gerrit/${platform} latest",
+      "-max-threads",
+      "0",
+      "-json-output",
+      "/path/to/tmp/json"
+    ],
+    "infra_step": true,
+    "name": "resolve CL deps.install infra/tools/luci/gerrit.ensure_installed",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@",
+      "@@@STEP_LOG_LINE@json.output@{@@@",
+      "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@    \"\": [@@@",
+      "@@@STEP_LOG_LINE@json.output@      {@@@",
+      "@@@STEP_LOG_LINE@json.output@        \"instance_id\": \"resolved-instance_id-of-latest----------\", @@@",
+      "@@@STEP_LOG_LINE@json.output@        \"package\": \"infra/tools/luci/gerrit/resolved-platform\"@@@",
+      "@@@STEP_LOG_LINE@json.output@      }@@@",
+      "@@@STEP_LOG_LINE@json.output@    ]@@@",
+      "@@@STEP_LOG_LINE@json.output@  }@@@",
+      "@@@STEP_LOG_LINE@json.output@}@@@",
+      "@@@STEP_LOG_END@json.output@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "[CACHE]/cipd/infra/tools/luci/gerrit/latest/gerrit",
+      "change-detail",
+      "-host",
+      "https://pigweed-review.googlesource.com",
+      "-input",
+      "{\"change_id\": \"1\", \"params\": {\"o\": [\"CURRENT_COMMIT\", \"CURRENT_REVISION\"]}}",
+      "-output",
+      "/path/to/tmp/json"
+    ],
+    "name": "resolve CL deps.details pigweed:1",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@",
+      "@@@STEP_LOG_LINE@json.output@{@@@",
+      "@@@STEP_LOG_LINE@json.output@  \"_number\": 1, @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"current_revision\": \"HASH\", @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"project\": \"project\", @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"revisions\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@    \"HASH\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@      \"_number\": 1, @@@",
+      "@@@STEP_LOG_LINE@json.output@      \"commit\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@        \"message\": \"Cq-Depend: pigweed:2\"@@@",
+      "@@@STEP_LOG_LINE@json.output@      }@@@",
+      "@@@STEP_LOG_LINE@json.output@    }@@@",
+      "@@@STEP_LOG_LINE@json.output@  }, @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"status\": \"NEW\"@@@",
+      "@@@STEP_LOG_LINE@json.output@}@@@",
+      "@@@STEP_LOG_END@json.output@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "resolve CL deps.resolve deps for pigweed:1",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@",
+      "@@@STEP_LINK@pigweed:2@https://pigweed-review.googlesource.com/2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "[CACHE]/cipd/infra/tools/luci/gerrit/latest/gerrit",
+      "change-detail",
+      "-host",
+      "https://pigweed-review.googlesource.com",
+      "-input",
+      "{\"change_id\": \"2\", \"params\": {\"o\": [\"CURRENT_COMMIT\", \"CURRENT_REVISION\"]}}",
+      "-output",
+      "/path/to/tmp/json"
+    ],
+    "name": "resolve CL deps.details pigweed:2",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@",
+      "@@@STEP_LOG_LINE@json.output@{@@@",
+      "@@@STEP_LOG_LINE@json.output@  \"_number\": 2, @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"current_revision\": \"HASH\", @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"project\": \"project\", @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"revisions\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@    \"HASH\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@      \"_number\": 1, @@@",
+      "@@@STEP_LOG_LINE@json.output@      \"commit\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@        \"message\": \"Cq-Depend: pigweed:1\"@@@",
+      "@@@STEP_LOG_LINE@json.output@      }@@@",
+      "@@@STEP_LOG_LINE@json.output@    }@@@",
+      "@@@STEP_LOG_LINE@json.output@  }, @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"status\": \"NEW\"@@@",
+      "@@@STEP_LOG_LINE@json.output@}@@@",
+      "@@@STEP_LOG_END@json.output@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "resolve CL deps.resolve deps for pigweed:2",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@",
+      "@@@STEP_LINK@pigweed:1@https://pigweed-review.googlesource.com/1@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "resolve CL deps.pass",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "final deps"
+  },
+  {
+    "cmd": [],
+    "name": "final deps.pigweed:2",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "name": "$result"
+  }
+]
\ No newline at end of file
diff --git a/recipe_modules/cq_deps/tests/full.expected/merged_ok.json b/recipe_modules/cq_deps/tests/full.expected/merged_ok.json
new file mode 100644
index 0000000..4ae92d9
--- /dev/null
+++ b/recipe_modules/cq_deps/tests/full.expected/merged_ok.json
@@ -0,0 +1,208 @@
+[
+  {
+    "cmd": [],
+    "name": "resolve CL deps",
+    "~followup_annotations": [
+      "@@@STEP_LINK@pigweed:2@https://pigweed-review.googlesource.com/2@@@",
+      "@@@STEP_LINK@pigweed:3@https://pigweed-review.googlesource.com/3@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "resolve CL deps.install infra/tools/luci/gerrit",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "ensure-directory",
+      "--mode",
+      "0777",
+      "[CACHE]/cipd/infra/tools/luci/gerrit/latest"
+    ],
+    "infra_step": true,
+    "name": "resolve CL deps.install infra/tools/luci/gerrit.ensure package directory",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "cipd",
+      "ensure",
+      "-root",
+      "[CACHE]/cipd/infra/tools/luci/gerrit/latest",
+      "-ensure-file",
+      "infra/tools/luci/gerrit/${platform} latest",
+      "-max-threads",
+      "0",
+      "-json-output",
+      "/path/to/tmp/json"
+    ],
+    "infra_step": true,
+    "name": "resolve CL deps.install infra/tools/luci/gerrit.ensure_installed",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@",
+      "@@@STEP_LOG_LINE@json.output@{@@@",
+      "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@    \"\": [@@@",
+      "@@@STEP_LOG_LINE@json.output@      {@@@",
+      "@@@STEP_LOG_LINE@json.output@        \"instance_id\": \"resolved-instance_id-of-latest----------\", @@@",
+      "@@@STEP_LOG_LINE@json.output@        \"package\": \"infra/tools/luci/gerrit/resolved-platform\"@@@",
+      "@@@STEP_LOG_LINE@json.output@      }@@@",
+      "@@@STEP_LOG_LINE@json.output@    ]@@@",
+      "@@@STEP_LOG_LINE@json.output@  }@@@",
+      "@@@STEP_LOG_LINE@json.output@}@@@",
+      "@@@STEP_LOG_END@json.output@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "[CACHE]/cipd/infra/tools/luci/gerrit/latest/gerrit",
+      "change-detail",
+      "-host",
+      "https://pigweed-review.googlesource.com",
+      "-input",
+      "{\"change_id\": \"1\", \"params\": {\"o\": [\"CURRENT_COMMIT\", \"CURRENT_REVISION\"]}}",
+      "-output",
+      "/path/to/tmp/json"
+    ],
+    "name": "resolve CL deps.details pigweed:1",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@",
+      "@@@STEP_LOG_LINE@json.output@{@@@",
+      "@@@STEP_LOG_LINE@json.output@  \"_number\": 1, @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"current_revision\": \"HASH\", @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"project\": \"project\", @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"revisions\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@    \"HASH\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@      \"_number\": 1, @@@",
+      "@@@STEP_LOG_LINE@json.output@      \"commit\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@        \"message\": \"Cq-Depend: pigweed:2\"@@@",
+      "@@@STEP_LOG_LINE@json.output@      }@@@",
+      "@@@STEP_LOG_LINE@json.output@    }@@@",
+      "@@@STEP_LOG_LINE@json.output@  }, @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"status\": \"NEW\"@@@",
+      "@@@STEP_LOG_LINE@json.output@}@@@",
+      "@@@STEP_LOG_END@json.output@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "resolve CL deps.resolve deps for pigweed:1",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@",
+      "@@@STEP_LINK@pigweed:2@https://pigweed-review.googlesource.com/2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "[CACHE]/cipd/infra/tools/luci/gerrit/latest/gerrit",
+      "change-detail",
+      "-host",
+      "https://pigweed-review.googlesource.com",
+      "-input",
+      "{\"change_id\": \"2\", \"params\": {\"o\": [\"CURRENT_COMMIT\", \"CURRENT_REVISION\"]}}",
+      "-output",
+      "/path/to/tmp/json"
+    ],
+    "name": "resolve CL deps.details pigweed:2",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@",
+      "@@@STEP_LOG_LINE@json.output@{@@@",
+      "@@@STEP_LOG_LINE@json.output@  \"_number\": 2, @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"current_revision\": \"HASH\", @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"project\": \"project\", @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"revisions\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@    \"HASH\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@      \"_number\": 1, @@@",
+      "@@@STEP_LOG_LINE@json.output@      \"commit\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@        \"message\": \"Cq-Depend: pigweed:3\"@@@",
+      "@@@STEP_LOG_LINE@json.output@      }@@@",
+      "@@@STEP_LOG_LINE@json.output@    }@@@",
+      "@@@STEP_LOG_LINE@json.output@  }, @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"status\": \"MERGED\"@@@",
+      "@@@STEP_LOG_LINE@json.output@}@@@",
+      "@@@STEP_LOG_END@json.output@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "resolve CL deps.resolve deps for pigweed:2",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@",
+      "@@@STEP_LINK@pigweed:3@https://pigweed-review.googlesource.com/3@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "[CACHE]/cipd/infra/tools/luci/gerrit/latest/gerrit",
+      "change-detail",
+      "-host",
+      "https://pigweed-review.googlesource.com",
+      "-input",
+      "{\"change_id\": \"3\", \"params\": {\"o\": [\"CURRENT_COMMIT\", \"CURRENT_REVISION\"]}}",
+      "-output",
+      "/path/to/tmp/json"
+    ],
+    "name": "resolve CL deps.details pigweed:3",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@",
+      "@@@STEP_LOG_LINE@json.output@{@@@",
+      "@@@STEP_LOG_LINE@json.output@  \"current_revision\": \"HASH\", @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"project\": \"project\", @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"revisions\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@    \"HASH\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@      \"_number\": 1, @@@",
+      "@@@STEP_LOG_LINE@json.output@      \"commit\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@        \"message\": \"\"@@@",
+      "@@@STEP_LOG_LINE@json.output@      }@@@",
+      "@@@STEP_LOG_LINE@json.output@    }@@@",
+      "@@@STEP_LOG_LINE@json.output@  }, @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"status\": \"NEW\"@@@",
+      "@@@STEP_LOG_LINE@json.output@}@@@",
+      "@@@STEP_LOG_END@json.output@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "resolve CL deps.resolve deps for pigweed:3",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "resolve CL deps.pass",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "final deps"
+  },
+  {
+    "cmd": [],
+    "name": "final deps.pigweed:2",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "final deps.pigweed:3",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "name": "$result"
+  }
+]
\ No newline at end of file
diff --git a/recipe_modules/cq_deps/tests/full.expected/merged_skipped.json b/recipe_modules/cq_deps/tests/full.expected/merged_skipped.json
new file mode 100644
index 0000000..caf1cae
--- /dev/null
+++ b/recipe_modules/cq_deps/tests/full.expected/merged_skipped.json
@@ -0,0 +1,145 @@
+[
+  {
+    "cmd": [],
+    "name": "resolve CL deps"
+  },
+  {
+    "cmd": [],
+    "name": "resolve CL deps.install infra/tools/luci/gerrit",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "ensure-directory",
+      "--mode",
+      "0777",
+      "[CACHE]/cipd/infra/tools/luci/gerrit/latest"
+    ],
+    "infra_step": true,
+    "name": "resolve CL deps.install infra/tools/luci/gerrit.ensure package directory",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "cipd",
+      "ensure",
+      "-root",
+      "[CACHE]/cipd/infra/tools/luci/gerrit/latest",
+      "-ensure-file",
+      "infra/tools/luci/gerrit/${platform} latest",
+      "-max-threads",
+      "0",
+      "-json-output",
+      "/path/to/tmp/json"
+    ],
+    "infra_step": true,
+    "name": "resolve CL deps.install infra/tools/luci/gerrit.ensure_installed",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@",
+      "@@@STEP_LOG_LINE@json.output@{@@@",
+      "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@    \"\": [@@@",
+      "@@@STEP_LOG_LINE@json.output@      {@@@",
+      "@@@STEP_LOG_LINE@json.output@        \"instance_id\": \"resolved-instance_id-of-latest----------\", @@@",
+      "@@@STEP_LOG_LINE@json.output@        \"package\": \"infra/tools/luci/gerrit/resolved-platform\"@@@",
+      "@@@STEP_LOG_LINE@json.output@      }@@@",
+      "@@@STEP_LOG_LINE@json.output@    ]@@@",
+      "@@@STEP_LOG_LINE@json.output@  }@@@",
+      "@@@STEP_LOG_LINE@json.output@}@@@",
+      "@@@STEP_LOG_END@json.output@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "[CACHE]/cipd/infra/tools/luci/gerrit/latest/gerrit",
+      "change-detail",
+      "-host",
+      "https://pigweed-review.googlesource.com",
+      "-input",
+      "{\"change_id\": \"1\", \"params\": {\"o\": [\"CURRENT_COMMIT\", \"CURRENT_REVISION\"]}}",
+      "-output",
+      "/path/to/tmp/json"
+    ],
+    "name": "resolve CL deps.details pigweed:1",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@",
+      "@@@STEP_LOG_LINE@json.output@{@@@",
+      "@@@STEP_LOG_LINE@json.output@  \"_number\": 1, @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"current_revision\": \"HASH\", @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"project\": \"project\", @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"revisions\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@    \"HASH\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@      \"_number\": 1, @@@",
+      "@@@STEP_LOG_LINE@json.output@      \"commit\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@        \"message\": \"Cq-Depend: pigweed:2\"@@@",
+      "@@@STEP_LOG_LINE@json.output@      }@@@",
+      "@@@STEP_LOG_LINE@json.output@    }@@@",
+      "@@@STEP_LOG_LINE@json.output@  }, @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"status\": \"NEW\"@@@",
+      "@@@STEP_LOG_LINE@json.output@}@@@",
+      "@@@STEP_LOG_END@json.output@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "resolve CL deps.resolve deps for pigweed:1",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@",
+      "@@@STEP_LINK@pigweed:2@https://pigweed-review.googlesource.com/2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "[CACHE]/cipd/infra/tools/luci/gerrit/latest/gerrit",
+      "change-detail",
+      "-host",
+      "https://pigweed-review.googlesource.com",
+      "-input",
+      "{\"change_id\": \"2\", \"params\": {\"o\": [\"CURRENT_COMMIT\", \"CURRENT_REVISION\"]}}",
+      "-output",
+      "/path/to/tmp/json"
+    ],
+    "name": "resolve CL deps.details pigweed:2",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@",
+      "@@@STEP_LOG_LINE@json.output@{@@@",
+      "@@@STEP_LOG_LINE@json.output@  \"_number\": 2, @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"current_revision\": \"HASH\", @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"project\": \"project\", @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"revisions\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@    \"HASH\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@      \"_number\": 1, @@@",
+      "@@@STEP_LOG_LINE@json.output@      \"commit\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@        \"message\": \"Cq-Depend: pigweed:3\"@@@",
+      "@@@STEP_LOG_LINE@json.output@      }@@@",
+      "@@@STEP_LOG_LINE@json.output@    }@@@",
+      "@@@STEP_LOG_LINE@json.output@  }, @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"status\": \"MERGED\"@@@",
+      "@@@STEP_LOG_LINE@json.output@}@@@",
+      "@@@STEP_LOG_END@json.output@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "resolve CL deps.pass",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "final deps"
+  },
+  {
+    "name": "$result"
+  }
+]
\ No newline at end of file
diff --git a/recipe_modules/cq_deps/tests/full.expected/multiline.json b/recipe_modules/cq_deps/tests/full.expected/multiline.json
new file mode 100644
index 0000000..2b1634d
--- /dev/null
+++ b/recipe_modules/cq_deps/tests/full.expected/multiline.json
@@ -0,0 +1,207 @@
+[
+  {
+    "cmd": [],
+    "name": "resolve CL deps",
+    "~followup_annotations": [
+      "@@@STEP_LINK@pigweed:2@https://pigweed-review.googlesource.com/2@@@",
+      "@@@STEP_LINK@pigweed:3@https://pigweed-review.googlesource.com/3@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "resolve CL deps.install infra/tools/luci/gerrit",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "ensure-directory",
+      "--mode",
+      "0777",
+      "[CACHE]/cipd/infra/tools/luci/gerrit/latest"
+    ],
+    "infra_step": true,
+    "name": "resolve CL deps.install infra/tools/luci/gerrit.ensure package directory",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "cipd",
+      "ensure",
+      "-root",
+      "[CACHE]/cipd/infra/tools/luci/gerrit/latest",
+      "-ensure-file",
+      "infra/tools/luci/gerrit/${platform} latest",
+      "-max-threads",
+      "0",
+      "-json-output",
+      "/path/to/tmp/json"
+    ],
+    "infra_step": true,
+    "name": "resolve CL deps.install infra/tools/luci/gerrit.ensure_installed",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@",
+      "@@@STEP_LOG_LINE@json.output@{@@@",
+      "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@    \"\": [@@@",
+      "@@@STEP_LOG_LINE@json.output@      {@@@",
+      "@@@STEP_LOG_LINE@json.output@        \"instance_id\": \"resolved-instance_id-of-latest----------\", @@@",
+      "@@@STEP_LOG_LINE@json.output@        \"package\": \"infra/tools/luci/gerrit/resolved-platform\"@@@",
+      "@@@STEP_LOG_LINE@json.output@      }@@@",
+      "@@@STEP_LOG_LINE@json.output@    ]@@@",
+      "@@@STEP_LOG_LINE@json.output@  }@@@",
+      "@@@STEP_LOG_LINE@json.output@}@@@",
+      "@@@STEP_LOG_END@json.output@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "[CACHE]/cipd/infra/tools/luci/gerrit/latest/gerrit",
+      "change-detail",
+      "-host",
+      "https://pigweed-review.googlesource.com",
+      "-input",
+      "{\"change_id\": \"1\", \"params\": {\"o\": [\"CURRENT_COMMIT\", \"CURRENT_REVISION\"]}}",
+      "-output",
+      "/path/to/tmp/json"
+    ],
+    "name": "resolve CL deps.details pigweed:1",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@",
+      "@@@STEP_LOG_LINE@json.output@{@@@",
+      "@@@STEP_LOG_LINE@json.output@  \"_number\": 1, @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"current_revision\": \"HASH\", @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"project\": \"project\", @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"revisions\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@    \"HASH\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@      \"_number\": 1, @@@",
+      "@@@STEP_LOG_LINE@json.output@      \"commit\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@        \"message\": \"Cq-Depend: pigweed:2\\nCq-Depend: pigweed:3\"@@@",
+      "@@@STEP_LOG_LINE@json.output@      }@@@",
+      "@@@STEP_LOG_LINE@json.output@    }@@@",
+      "@@@STEP_LOG_LINE@json.output@  }, @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"status\": \"NEW\"@@@",
+      "@@@STEP_LOG_LINE@json.output@}@@@",
+      "@@@STEP_LOG_END@json.output@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "resolve CL deps.resolve deps for pigweed:1",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@",
+      "@@@STEP_LINK@pigweed:2@https://pigweed-review.googlesource.com/2@@@",
+      "@@@STEP_LINK@pigweed:3@https://pigweed-review.googlesource.com/3@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "[CACHE]/cipd/infra/tools/luci/gerrit/latest/gerrit",
+      "change-detail",
+      "-host",
+      "https://pigweed-review.googlesource.com",
+      "-input",
+      "{\"change_id\": \"2\", \"params\": {\"o\": [\"CURRENT_COMMIT\", \"CURRENT_REVISION\"]}}",
+      "-output",
+      "/path/to/tmp/json"
+    ],
+    "name": "resolve CL deps.details pigweed:2",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@",
+      "@@@STEP_LOG_LINE@json.output@{@@@",
+      "@@@STEP_LOG_LINE@json.output@  \"current_revision\": \"HASH\", @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"project\": \"project\", @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"revisions\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@    \"HASH\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@      \"_number\": 1, @@@",
+      "@@@STEP_LOG_LINE@json.output@      \"commit\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@        \"message\": \"\"@@@",
+      "@@@STEP_LOG_LINE@json.output@      }@@@",
+      "@@@STEP_LOG_LINE@json.output@    }@@@",
+      "@@@STEP_LOG_LINE@json.output@  }, @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"status\": \"NEW\"@@@",
+      "@@@STEP_LOG_LINE@json.output@}@@@",
+      "@@@STEP_LOG_END@json.output@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "[CACHE]/cipd/infra/tools/luci/gerrit/latest/gerrit",
+      "change-detail",
+      "-host",
+      "https://pigweed-review.googlesource.com",
+      "-input",
+      "{\"change_id\": \"3\", \"params\": {\"o\": [\"CURRENT_COMMIT\", \"CURRENT_REVISION\"]}}",
+      "-output",
+      "/path/to/tmp/json"
+    ],
+    "name": "resolve CL deps.details pigweed:3",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@",
+      "@@@STEP_LOG_LINE@json.output@{@@@",
+      "@@@STEP_LOG_LINE@json.output@  \"current_revision\": \"HASH\", @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"project\": \"project\", @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"revisions\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@    \"HASH\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@      \"_number\": 1, @@@",
+      "@@@STEP_LOG_LINE@json.output@      \"commit\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@        \"message\": \"\"@@@",
+      "@@@STEP_LOG_LINE@json.output@      }@@@",
+      "@@@STEP_LOG_LINE@json.output@    }@@@",
+      "@@@STEP_LOG_LINE@json.output@  }, @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"status\": \"NEW\"@@@",
+      "@@@STEP_LOG_LINE@json.output@}@@@",
+      "@@@STEP_LOG_END@json.output@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "resolve CL deps.resolve deps for pigweed:3",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "resolve CL deps.resolve deps for pigweed:2",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "resolve CL deps.pass",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "final deps"
+  },
+  {
+    "cmd": [],
+    "name": "final deps.pigweed:2",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "final deps.pigweed:3",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "name": "$result"
+  }
+]
\ No newline at end of file
diff --git a/recipe_modules/cq_deps/tests/full.expected/no_space.json b/recipe_modules/cq_deps/tests/full.expected/no_space.json
new file mode 100644
index 0000000..c8a7182
--- /dev/null
+++ b/recipe_modules/cq_deps/tests/full.expected/no_space.json
@@ -0,0 +1,207 @@
+[
+  {
+    "cmd": [],
+    "name": "resolve CL deps",
+    "~followup_annotations": [
+      "@@@STEP_LINK@pigweed:2@https://pigweed-review.googlesource.com/2@@@",
+      "@@@STEP_LINK@pigweed:3@https://pigweed-review.googlesource.com/3@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "resolve CL deps.install infra/tools/luci/gerrit",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "ensure-directory",
+      "--mode",
+      "0777",
+      "[CACHE]/cipd/infra/tools/luci/gerrit/latest"
+    ],
+    "infra_step": true,
+    "name": "resolve CL deps.install infra/tools/luci/gerrit.ensure package directory",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "cipd",
+      "ensure",
+      "-root",
+      "[CACHE]/cipd/infra/tools/luci/gerrit/latest",
+      "-ensure-file",
+      "infra/tools/luci/gerrit/${platform} latest",
+      "-max-threads",
+      "0",
+      "-json-output",
+      "/path/to/tmp/json"
+    ],
+    "infra_step": true,
+    "name": "resolve CL deps.install infra/tools/luci/gerrit.ensure_installed",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@",
+      "@@@STEP_LOG_LINE@json.output@{@@@",
+      "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@    \"\": [@@@",
+      "@@@STEP_LOG_LINE@json.output@      {@@@",
+      "@@@STEP_LOG_LINE@json.output@        \"instance_id\": \"resolved-instance_id-of-latest----------\", @@@",
+      "@@@STEP_LOG_LINE@json.output@        \"package\": \"infra/tools/luci/gerrit/resolved-platform\"@@@",
+      "@@@STEP_LOG_LINE@json.output@      }@@@",
+      "@@@STEP_LOG_LINE@json.output@    ]@@@",
+      "@@@STEP_LOG_LINE@json.output@  }@@@",
+      "@@@STEP_LOG_LINE@json.output@}@@@",
+      "@@@STEP_LOG_END@json.output@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "[CACHE]/cipd/infra/tools/luci/gerrit/latest/gerrit",
+      "change-detail",
+      "-host",
+      "https://pigweed-review.googlesource.com",
+      "-input",
+      "{\"change_id\": \"1\", \"params\": {\"o\": [\"CURRENT_COMMIT\", \"CURRENT_REVISION\"]}}",
+      "-output",
+      "/path/to/tmp/json"
+    ],
+    "name": "resolve CL deps.details pigweed:1",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@",
+      "@@@STEP_LOG_LINE@json.output@{@@@",
+      "@@@STEP_LOG_LINE@json.output@  \"_number\": 1, @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"current_revision\": \"HASH\", @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"project\": \"project\", @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"revisions\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@    \"HASH\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@      \"_number\": 1, @@@",
+      "@@@STEP_LOG_LINE@json.output@      \"commit\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@        \"message\": \"Cq-Depend: pigweed:2,pigweed:3\"@@@",
+      "@@@STEP_LOG_LINE@json.output@      }@@@",
+      "@@@STEP_LOG_LINE@json.output@    }@@@",
+      "@@@STEP_LOG_LINE@json.output@  }, @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"status\": \"NEW\"@@@",
+      "@@@STEP_LOG_LINE@json.output@}@@@",
+      "@@@STEP_LOG_END@json.output@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "resolve CL deps.resolve deps for pigweed:1",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@",
+      "@@@STEP_LINK@pigweed:2@https://pigweed-review.googlesource.com/2@@@",
+      "@@@STEP_LINK@pigweed:3@https://pigweed-review.googlesource.com/3@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "[CACHE]/cipd/infra/tools/luci/gerrit/latest/gerrit",
+      "change-detail",
+      "-host",
+      "https://pigweed-review.googlesource.com",
+      "-input",
+      "{\"change_id\": \"2\", \"params\": {\"o\": [\"CURRENT_COMMIT\", \"CURRENT_REVISION\"]}}",
+      "-output",
+      "/path/to/tmp/json"
+    ],
+    "name": "resolve CL deps.details pigweed:2",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@",
+      "@@@STEP_LOG_LINE@json.output@{@@@",
+      "@@@STEP_LOG_LINE@json.output@  \"current_revision\": \"HASH\", @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"project\": \"project\", @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"revisions\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@    \"HASH\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@      \"_number\": 1, @@@",
+      "@@@STEP_LOG_LINE@json.output@      \"commit\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@        \"message\": \"\"@@@",
+      "@@@STEP_LOG_LINE@json.output@      }@@@",
+      "@@@STEP_LOG_LINE@json.output@    }@@@",
+      "@@@STEP_LOG_LINE@json.output@  }, @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"status\": \"NEW\"@@@",
+      "@@@STEP_LOG_LINE@json.output@}@@@",
+      "@@@STEP_LOG_END@json.output@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "[CACHE]/cipd/infra/tools/luci/gerrit/latest/gerrit",
+      "change-detail",
+      "-host",
+      "https://pigweed-review.googlesource.com",
+      "-input",
+      "{\"change_id\": \"3\", \"params\": {\"o\": [\"CURRENT_COMMIT\", \"CURRENT_REVISION\"]}}",
+      "-output",
+      "/path/to/tmp/json"
+    ],
+    "name": "resolve CL deps.details pigweed:3",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@",
+      "@@@STEP_LOG_LINE@json.output@{@@@",
+      "@@@STEP_LOG_LINE@json.output@  \"current_revision\": \"HASH\", @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"project\": \"project\", @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"revisions\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@    \"HASH\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@      \"_number\": 1, @@@",
+      "@@@STEP_LOG_LINE@json.output@      \"commit\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@        \"message\": \"\"@@@",
+      "@@@STEP_LOG_LINE@json.output@      }@@@",
+      "@@@STEP_LOG_LINE@json.output@    }@@@",
+      "@@@STEP_LOG_LINE@json.output@  }, @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"status\": \"NEW\"@@@",
+      "@@@STEP_LOG_LINE@json.output@}@@@",
+      "@@@STEP_LOG_END@json.output@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "resolve CL deps.resolve deps for pigweed:3",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "resolve CL deps.resolve deps for pigweed:2",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "resolve CL deps.pass",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "final deps"
+  },
+  {
+    "cmd": [],
+    "name": "final deps.pigweed:2",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "final deps.pigweed:3",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "name": "$result"
+  }
+]
\ No newline at end of file
diff --git a/recipe_modules/cq_deps/tests/full.expected/one.json b/recipe_modules/cq_deps/tests/full.expected/one.json
new file mode 100644
index 0000000..c3bdf59
--- /dev/null
+++ b/recipe_modules/cq_deps/tests/full.expected/one.json
@@ -0,0 +1,161 @@
+[
+  {
+    "cmd": [],
+    "name": "resolve CL deps",
+    "~followup_annotations": [
+      "@@@STEP_LINK@pigweed:2@https://pigweed-review.googlesource.com/2@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "resolve CL deps.install infra/tools/luci/gerrit",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "ensure-directory",
+      "--mode",
+      "0777",
+      "[CACHE]/cipd/infra/tools/luci/gerrit/latest"
+    ],
+    "infra_step": true,
+    "name": "resolve CL deps.install infra/tools/luci/gerrit.ensure package directory",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "cipd",
+      "ensure",
+      "-root",
+      "[CACHE]/cipd/infra/tools/luci/gerrit/latest",
+      "-ensure-file",
+      "infra/tools/luci/gerrit/${platform} latest",
+      "-max-threads",
+      "0",
+      "-json-output",
+      "/path/to/tmp/json"
+    ],
+    "infra_step": true,
+    "name": "resolve CL deps.install infra/tools/luci/gerrit.ensure_installed",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@",
+      "@@@STEP_LOG_LINE@json.output@{@@@",
+      "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@    \"\": [@@@",
+      "@@@STEP_LOG_LINE@json.output@      {@@@",
+      "@@@STEP_LOG_LINE@json.output@        \"instance_id\": \"resolved-instance_id-of-latest----------\", @@@",
+      "@@@STEP_LOG_LINE@json.output@        \"package\": \"infra/tools/luci/gerrit/resolved-platform\"@@@",
+      "@@@STEP_LOG_LINE@json.output@      }@@@",
+      "@@@STEP_LOG_LINE@json.output@    ]@@@",
+      "@@@STEP_LOG_LINE@json.output@  }@@@",
+      "@@@STEP_LOG_LINE@json.output@}@@@",
+      "@@@STEP_LOG_END@json.output@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "[CACHE]/cipd/infra/tools/luci/gerrit/latest/gerrit",
+      "change-detail",
+      "-host",
+      "https://pigweed-review.googlesource.com",
+      "-input",
+      "{\"change_id\": \"1\", \"params\": {\"o\": [\"CURRENT_COMMIT\", \"CURRENT_REVISION\"]}}",
+      "-output",
+      "/path/to/tmp/json"
+    ],
+    "name": "resolve CL deps.details pigweed:1",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@",
+      "@@@STEP_LOG_LINE@json.output@{@@@",
+      "@@@STEP_LOG_LINE@json.output@  \"_number\": 1, @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"current_revision\": \"HASH\", @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"project\": \"project\", @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"revisions\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@    \"HASH\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@      \"_number\": 1, @@@",
+      "@@@STEP_LOG_LINE@json.output@      \"commit\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@        \"message\": \"Cq-Depend: pigweed:2\"@@@",
+      "@@@STEP_LOG_LINE@json.output@      }@@@",
+      "@@@STEP_LOG_LINE@json.output@    }@@@",
+      "@@@STEP_LOG_LINE@json.output@  }, @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"status\": \"NEW\"@@@",
+      "@@@STEP_LOG_LINE@json.output@}@@@",
+      "@@@STEP_LOG_END@json.output@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "resolve CL deps.resolve deps for pigweed:1",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@",
+      "@@@STEP_LINK@pigweed:2@https://pigweed-review.googlesource.com/2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "[CACHE]/cipd/infra/tools/luci/gerrit/latest/gerrit",
+      "change-detail",
+      "-host",
+      "https://pigweed-review.googlesource.com",
+      "-input",
+      "{\"change_id\": \"2\", \"params\": {\"o\": [\"CURRENT_COMMIT\", \"CURRENT_REVISION\"]}}",
+      "-output",
+      "/path/to/tmp/json"
+    ],
+    "name": "resolve CL deps.details pigweed:2",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@",
+      "@@@STEP_LOG_LINE@json.output@{@@@",
+      "@@@STEP_LOG_LINE@json.output@  \"current_revision\": \"HASH\", @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"project\": \"project\", @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"revisions\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@    \"HASH\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@      \"_number\": 1, @@@",
+      "@@@STEP_LOG_LINE@json.output@      \"commit\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@        \"message\": \"\"@@@",
+      "@@@STEP_LOG_LINE@json.output@      }@@@",
+      "@@@STEP_LOG_LINE@json.output@    }@@@",
+      "@@@STEP_LOG_LINE@json.output@  }, @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"status\": \"NEW\"@@@",
+      "@@@STEP_LOG_LINE@json.output@}@@@",
+      "@@@STEP_LOG_END@json.output@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "resolve CL deps.resolve deps for pigweed:2",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "resolve CL deps.pass",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "final deps"
+  },
+  {
+    "cmd": [],
+    "name": "final deps.pigweed:2",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "name": "$result"
+  }
+]
\ No newline at end of file
diff --git a/recipe_modules/cq_deps/tests/full.expected/two.json b/recipe_modules/cq_deps/tests/full.expected/two.json
new file mode 100644
index 0000000..919216e
--- /dev/null
+++ b/recipe_modules/cq_deps/tests/full.expected/two.json
@@ -0,0 +1,207 @@
+[
+  {
+    "cmd": [],
+    "name": "resolve CL deps",
+    "~followup_annotations": [
+      "@@@STEP_LINK@pigweed:2@https://pigweed-review.googlesource.com/2@@@",
+      "@@@STEP_LINK@pigweed:3@https://pigweed-review.googlesource.com/3@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "resolve CL deps.install infra/tools/luci/gerrit",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "ensure-directory",
+      "--mode",
+      "0777",
+      "[CACHE]/cipd/infra/tools/luci/gerrit/latest"
+    ],
+    "infra_step": true,
+    "name": "resolve CL deps.install infra/tools/luci/gerrit.ensure package directory",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "cipd",
+      "ensure",
+      "-root",
+      "[CACHE]/cipd/infra/tools/luci/gerrit/latest",
+      "-ensure-file",
+      "infra/tools/luci/gerrit/${platform} latest",
+      "-max-threads",
+      "0",
+      "-json-output",
+      "/path/to/tmp/json"
+    ],
+    "infra_step": true,
+    "name": "resolve CL deps.install infra/tools/luci/gerrit.ensure_installed",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@",
+      "@@@STEP_LOG_LINE@json.output@{@@@",
+      "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@    \"\": [@@@",
+      "@@@STEP_LOG_LINE@json.output@      {@@@",
+      "@@@STEP_LOG_LINE@json.output@        \"instance_id\": \"resolved-instance_id-of-latest----------\", @@@",
+      "@@@STEP_LOG_LINE@json.output@        \"package\": \"infra/tools/luci/gerrit/resolved-platform\"@@@",
+      "@@@STEP_LOG_LINE@json.output@      }@@@",
+      "@@@STEP_LOG_LINE@json.output@    ]@@@",
+      "@@@STEP_LOG_LINE@json.output@  }@@@",
+      "@@@STEP_LOG_LINE@json.output@}@@@",
+      "@@@STEP_LOG_END@json.output@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "[CACHE]/cipd/infra/tools/luci/gerrit/latest/gerrit",
+      "change-detail",
+      "-host",
+      "https://pigweed-review.googlesource.com",
+      "-input",
+      "{\"change_id\": \"1\", \"params\": {\"o\": [\"CURRENT_COMMIT\", \"CURRENT_REVISION\"]}}",
+      "-output",
+      "/path/to/tmp/json"
+    ],
+    "name": "resolve CL deps.details pigweed:1",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@",
+      "@@@STEP_LOG_LINE@json.output@{@@@",
+      "@@@STEP_LOG_LINE@json.output@  \"_number\": 1, @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"current_revision\": \"HASH\", @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"project\": \"project\", @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"revisions\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@    \"HASH\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@      \"_number\": 1, @@@",
+      "@@@STEP_LOG_LINE@json.output@      \"commit\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@        \"message\": \"Cq-Depend: pigweed:2, pigweed:3\"@@@",
+      "@@@STEP_LOG_LINE@json.output@      }@@@",
+      "@@@STEP_LOG_LINE@json.output@    }@@@",
+      "@@@STEP_LOG_LINE@json.output@  }, @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"status\": \"NEW\"@@@",
+      "@@@STEP_LOG_LINE@json.output@}@@@",
+      "@@@STEP_LOG_END@json.output@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "resolve CL deps.resolve deps for pigweed:1",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@",
+      "@@@STEP_LINK@pigweed:2@https://pigweed-review.googlesource.com/2@@@",
+      "@@@STEP_LINK@pigweed:3@https://pigweed-review.googlesource.com/3@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "[CACHE]/cipd/infra/tools/luci/gerrit/latest/gerrit",
+      "change-detail",
+      "-host",
+      "https://pigweed-review.googlesource.com",
+      "-input",
+      "{\"change_id\": \"2\", \"params\": {\"o\": [\"CURRENT_COMMIT\", \"CURRENT_REVISION\"]}}",
+      "-output",
+      "/path/to/tmp/json"
+    ],
+    "name": "resolve CL deps.details pigweed:2",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@",
+      "@@@STEP_LOG_LINE@json.output@{@@@",
+      "@@@STEP_LOG_LINE@json.output@  \"current_revision\": \"HASH\", @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"project\": \"project\", @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"revisions\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@    \"HASH\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@      \"_number\": 1, @@@",
+      "@@@STEP_LOG_LINE@json.output@      \"commit\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@        \"message\": \"\"@@@",
+      "@@@STEP_LOG_LINE@json.output@      }@@@",
+      "@@@STEP_LOG_LINE@json.output@    }@@@",
+      "@@@STEP_LOG_LINE@json.output@  }, @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"status\": \"NEW\"@@@",
+      "@@@STEP_LOG_LINE@json.output@}@@@",
+      "@@@STEP_LOG_END@json.output@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "[CACHE]/cipd/infra/tools/luci/gerrit/latest/gerrit",
+      "change-detail",
+      "-host",
+      "https://pigweed-review.googlesource.com",
+      "-input",
+      "{\"change_id\": \"3\", \"params\": {\"o\": [\"CURRENT_COMMIT\", \"CURRENT_REVISION\"]}}",
+      "-output",
+      "/path/to/tmp/json"
+    ],
+    "name": "resolve CL deps.details pigweed:3",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@",
+      "@@@STEP_LOG_LINE@json.output@{@@@",
+      "@@@STEP_LOG_LINE@json.output@  \"current_revision\": \"HASH\", @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"project\": \"project\", @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"revisions\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@    \"HASH\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@      \"_number\": 1, @@@",
+      "@@@STEP_LOG_LINE@json.output@      \"commit\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@        \"message\": \"\"@@@",
+      "@@@STEP_LOG_LINE@json.output@      }@@@",
+      "@@@STEP_LOG_LINE@json.output@    }@@@",
+      "@@@STEP_LOG_LINE@json.output@  }, @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"status\": \"NEW\"@@@",
+      "@@@STEP_LOG_LINE@json.output@}@@@",
+      "@@@STEP_LOG_END@json.output@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "resolve CL deps.resolve deps for pigweed:3",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "resolve CL deps.resolve deps for pigweed:2",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "resolve CL deps.pass",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "final deps"
+  },
+  {
+    "cmd": [],
+    "name": "final deps.pigweed:2",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "final deps.pigweed:3",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "name": "$result"
+  }
+]
\ No newline at end of file
diff --git a/recipe_modules/cq_deps/tests/full.py b/recipe_modules/cq_deps/tests/full.py
new file mode 100644
index 0000000..af6d19e
--- /dev/null
+++ b/recipe_modules/cq_deps/tests/full.py
@@ -0,0 +1,165 @@
+# Copyright 2020 The Pigweed Authors
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may not
+# use this file except in compliance with the License. You may obtain a copy of
+# the License at
+#
+#     https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations under
+# the License.
+"""Full test of the cq_deps module."""
+
+from recipe_engine.config import List
+from recipe_engine.recipe_api import Property
+
+DEPS = [
+    'fuchsia/status_check',
+    'pigweed/cq_deps',
+    'recipe_engine/properties',
+    'recipe_engine/step',
+]
+
+PROPERTIES = {
+    'revision': Property(kind=str, default=None),
+    'statuses': Property(kind=List(str), default=('NEW',)),
+}
+
+
+def RunSteps(api, revision, statuses):
+    deps, errors = api.cq_deps.resolve(
+        'pigweed', revision or 1, statuses=statuses,
+    )
+
+    with api.step.nest('final deps'):
+        for dep in deps:
+            with api.step.nest(dep.name):
+                pass
+
+    if errors:
+        with api.step.nest('errors'):
+            for error in errors:
+                with api.step.nest(error.name):
+                    pass
+
+
+def GenTests(api):
+    yield (
+        api.status_check.test('hash')
+        + api.properties(revision='b' * 40)
+        + api.cq_deps.details('pigweed:1', '')
+    )
+
+    yield (
+        api.status_check.test('one')
+        + api.cq_deps.details('pigweed:1', 'Cq-Depend: pigweed:2')
+        + api.cq_deps.has_deps('pigweed:2')
+        + api.cq_deps.lacks_deps('pigweed:1')
+    )
+
+    yield (
+        api.status_check.test('two')
+        + api.cq_deps.details('pigweed:1', 'Cq-Depend: pigweed:2, pigweed:3')
+        + api.cq_deps.has_deps('pigweed:2', 'pigweed:3')
+    )
+
+    yield (
+        api.status_check.test('no_space')
+        + api.cq_deps.details('pigweed:1', 'Cq-Depend: pigweed:2,pigweed:3')
+        + api.cq_deps.has_deps('pigweed:2', 'pigweed:3')
+    )
+
+    yield (
+        api.status_check.test('loop_two')
+        + api.cq_deps.details('pigweed:1', 'Cq-Depend: pigweed:2')
+        + api.cq_deps.details('pigweed:2', 'Cq-Depend: pigweed:1')
+        + api.cq_deps.has_deps('pigweed:2')
+    )
+
+    yield (
+        api.status_check.test('loop_three')
+        + api.cq_deps.details('pigweed:1', 'Cq-Depend: pigweed:2')
+        + api.cq_deps.details('pigweed:2', 'Cq-Depend: pigweed:3')
+        + api.cq_deps.details('pigweed:3', 'Cq-Depend: pigweed:1')
+        + api.cq_deps.has_deps('pigweed:2', 'pigweed:3')
+    )
+
+    yield (
+        api.status_check.test('abandoned')
+        + api.cq_deps.details('pigweed:1', 'Cq-Depend: pigweed:2')
+        + api.cq_deps.details(
+            'pigweed:2', 'Cq-Depend: pigweed:3', status='ABANDONED'
+        )
+        + api.cq_deps.lacks_deps('pigweed:2')
+    )
+
+    yield (
+        api.status_check.test('merged_skipped')
+        + api.properties()
+        + api.cq_deps.details('pigweed:1', 'Cq-Depend: pigweed:2')
+        + api.cq_deps.details(
+            'pigweed:2', 'Cq-Depend: pigweed:3', status='MERGED'
+        )
+        + api.cq_deps.lacks_deps('pigweed:2', 'pigweed:3')
+    )
+
+    yield (
+        api.status_check.test('merged_ok')
+        + api.properties(statuses=('NEW', 'MERGED'))
+        + api.cq_deps.details('pigweed:1', 'Cq-Depend: pigweed:2')
+        + api.cq_deps.details(
+            'pigweed:2', 'Cq-Depend: pigweed:3', status='MERGED'
+        )
+        + api.cq_deps.has_deps('pigweed:2', 'pigweed:3')
+    )
+
+    yield (
+        api.status_check.test('bad_field_name')
+        + api.cq_deps.details('pigweed:1', 'Depends: pigweed:2')
+        + api.cq_deps.lacks_deps('pigweed:2')
+    )
+
+    yield (
+        api.status_check.test('commas')
+        + api.cq_deps.details('pigweed:1', 'Cq-Depend: ,,pigweed:2,')
+        + api.cq_deps.has_deps('pigweed:2')
+    )
+
+    yield (
+        api.status_check.test('error')
+        + api.cq_deps.details('pigweed:1', 'Cq-Depend: pigweed:2')
+        + api.cq_deps.transient('pigweed:2')
+        + api.cq_deps.transient('pigweed:2', n=2)
+        + api.cq_deps.transient('pigweed:2', n=3)
+        + api.cq_deps.transient('pigweed:2', n=4)
+        + api.cq_deps.transient('pigweed:2', n=5)
+        + api.cq_deps.lacks_deps('forbidden:1')
+    )
+
+    yield (
+        api.status_check.test('error_recovery')
+        + api.cq_deps.details('pigweed:1', 'Cq-Depend: pigweed:2')
+        + api.cq_deps.transient('pigweed:2')
+        + api.cq_deps.transient('pigweed:2', n=2)
+        + api.cq_deps.transient('pigweed:2', n=3)
+        + api.cq_deps.details('pigweed:2', '', n=4)
+        + api.cq_deps.has_deps('pigweed:2')
+    )
+
+    yield (
+        api.status_check.test('forbidden')
+        + api.cq_deps.details('pigweed:1', 'Cq-Depend: forbidden:1')
+        + api.cq_deps.forbidden('forbidden:1')
+        + api.cq_deps.lacks_deps('forbidden:1')
+    )
+
+    yield (
+        api.status_check.test('multiline')
+        + api.cq_deps.details(
+            'pigweed:1', 'Cq-Depend: pigweed:2\nCq-Depend: pigweed:3'
+        )
+        + api.cq_deps.has_deps('pigweed:2', 'pigweed:3')
+    )
diff --git a/recipe_modules/roll_util/__init__.py b/recipe_modules/roll_util/__init__.py
index 38b9ef2..8faeb16 100644
--- a/recipe_modules/roll_util/__init__.py
+++ b/recipe_modules/roll_util/__init__.py
@@ -16,6 +16,7 @@
 
 DEPS = [
     'fuchsia/git',
+    'recipe_engine/buildbucket',
     'recipe_engine/context',
     'recipe_engine/raw_io',
     'recipe_engine/step',
diff --git a/recipes/luci_config.expected/starlark.json b/recipes/luci_config.expected/starlark.json
index dda0f9a..f51f91d 100644
--- a/recipes/luci_config.expected/starlark.json
+++ b/recipes/luci_config.expected/starlark.json
@@ -101,6 +101,57 @@
   },
   {
     "cmd": [],
+    "name": "checkout pigweed.change data.process gerrit changes.resolve CL deps",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@3@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "[CACHE]/cipd/infra/tools/luci/gerrit/latest/gerrit",
+      "change-detail",
+      "-host",
+      "https://pigweed-review.googlesource.com",
+      "-input",
+      "{\"change_id\": \"123456\", \"params\": {\"o\": [\"CURRENT_COMMIT\", \"CURRENT_REVISION\"]}}",
+      "-output",
+      "/path/to/tmp/json"
+    ],
+    "name": "checkout pigweed.change data.process gerrit changes.resolve CL deps.details pigweed:123456",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@4@@@",
+      "@@@STEP_LOG_LINE@json.output@{@@@",
+      "@@@STEP_LOG_LINE@json.output@  \"current_revision\": \"HASH\", @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"project\": \"project\", @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"revisions\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@    \"HASH\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@      \"_number\": 1, @@@",
+      "@@@STEP_LOG_LINE@json.output@      \"commit\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@        \"message\": \"\"@@@",
+      "@@@STEP_LOG_LINE@json.output@      }@@@",
+      "@@@STEP_LOG_LINE@json.output@    }@@@",
+      "@@@STEP_LOG_LINE@json.output@  }, @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"status\": \"NEW\"@@@",
+      "@@@STEP_LOG_LINE@json.output@}@@@",
+      "@@@STEP_LOG_END@json.output@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "checkout pigweed.change data.process gerrit changes.resolve CL deps.resolve deps for pigweed:123456",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@4@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "checkout pigweed.change data.process gerrit changes.resolve CL deps.pass",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@4@@@"
+    ]
+  },
+  {
+    "cmd": [],
     "name": "checkout pigweed.change data.changes",
     "~followup_annotations": [
       "@@@STEP_NEST_LEVEL@2@@@"
diff --git a/recipes/pw_presubmit.expected/step.json b/recipes/pw_presubmit.expected/step.json
index 2acc9f8..19e3acf 100644
--- a/recipes/pw_presubmit.expected/step.json
+++ b/recipes/pw_presubmit.expected/step.json
@@ -101,6 +101,57 @@
   },
   {
     "cmd": [],
+    "name": "checkout pigweed.change data.process gerrit changes.resolve CL deps",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@3@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "[CACHE]/cipd/infra/tools/luci/gerrit/latest/gerrit",
+      "change-detail",
+      "-host",
+      "https://pigweed-review.googlesource.com",
+      "-input",
+      "{\"change_id\": \"123456\", \"params\": {\"o\": [\"CURRENT_COMMIT\", \"CURRENT_REVISION\"]}}",
+      "-output",
+      "/path/to/tmp/json"
+    ],
+    "name": "checkout pigweed.change data.process gerrit changes.resolve CL deps.details pigweed:123456",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@4@@@",
+      "@@@STEP_LOG_LINE@json.output@{@@@",
+      "@@@STEP_LOG_LINE@json.output@  \"current_revision\": \"HASH\", @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"project\": \"project\", @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"revisions\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@    \"HASH\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@      \"_number\": 1, @@@",
+      "@@@STEP_LOG_LINE@json.output@      \"commit\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@        \"message\": \"\"@@@",
+      "@@@STEP_LOG_LINE@json.output@      }@@@",
+      "@@@STEP_LOG_LINE@json.output@    }@@@",
+      "@@@STEP_LOG_LINE@json.output@  }, @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"status\": \"NEW\"@@@",
+      "@@@STEP_LOG_LINE@json.output@}@@@",
+      "@@@STEP_LOG_END@json.output@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "checkout pigweed.change data.process gerrit changes.resolve CL deps.resolve deps for pigweed:123456",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@4@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "checkout pigweed.change data.process gerrit changes.resolve CL deps.pass",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@4@@@"
+    ]
+  },
+  {
+    "cmd": [],
     "name": "checkout pigweed.change data.changes",
     "~followup_annotations": [
       "@@@STEP_NEST_LEVEL@2@@@"
diff --git a/recipes/pw_presubmit_container.expected/step.json b/recipes/pw_presubmit_container.expected/step.json
index cff737a..b692246 100644
--- a/recipes/pw_presubmit_container.expected/step.json
+++ b/recipes/pw_presubmit_container.expected/step.json
@@ -101,6 +101,57 @@
   },
   {
     "cmd": [],
+    "name": "checkout pigweed.change data.process gerrit changes.resolve CL deps",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@3@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "[CACHE]/cipd/infra/tools/luci/gerrit/latest/gerrit",
+      "change-detail",
+      "-host",
+      "https://pigweed-review.googlesource.com",
+      "-input",
+      "{\"change_id\": \"123456\", \"params\": {\"o\": [\"CURRENT_COMMIT\", \"CURRENT_REVISION\"]}}",
+      "-output",
+      "/path/to/tmp/json"
+    ],
+    "name": "checkout pigweed.change data.process gerrit changes.resolve CL deps.details pigweed:123456",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@4@@@",
+      "@@@STEP_LOG_LINE@json.output@{@@@",
+      "@@@STEP_LOG_LINE@json.output@  \"current_revision\": \"HASH\", @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"project\": \"project\", @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"revisions\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@    \"HASH\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@      \"_number\": 1, @@@",
+      "@@@STEP_LOG_LINE@json.output@      \"commit\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@        \"message\": \"\"@@@",
+      "@@@STEP_LOG_LINE@json.output@      }@@@",
+      "@@@STEP_LOG_LINE@json.output@    }@@@",
+      "@@@STEP_LOG_LINE@json.output@  }, @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"status\": \"NEW\"@@@",
+      "@@@STEP_LOG_LINE@json.output@}@@@",
+      "@@@STEP_LOG_END@json.output@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "checkout pigweed.change data.process gerrit changes.resolve CL deps.resolve deps for pigweed:123456",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@4@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "checkout pigweed.change data.process gerrit changes.resolve CL deps.pass",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@4@@@"
+    ]
+  },
+  {
+    "cmd": [],
     "name": "checkout pigweed.change data.changes",
     "~followup_annotations": [
       "@@@STEP_NEST_LEVEL@2@@@"
diff --git a/recipes/recipes.expected/cq_try.json b/recipes/recipes.expected/cq_try.json
index 25cffff..5e2be65 100644
--- a/recipes/recipes.expected/cq_try.json
+++ b/recipes/recipes.expected/cq_try.json
@@ -101,6 +101,57 @@
   },
   {
     "cmd": [],
+    "name": "checkout pigweed.change data.process gerrit changes.resolve CL deps",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@3@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "[CACHE]/cipd/infra/tools/luci/gerrit/latest/gerrit",
+      "change-detail",
+      "-host",
+      "https://pigweed-review.googlesource.com",
+      "-input",
+      "{\"change_id\": \"123456\", \"params\": {\"o\": [\"CURRENT_COMMIT\", \"CURRENT_REVISION\"]}}",
+      "-output",
+      "/path/to/tmp/json"
+    ],
+    "name": "checkout pigweed.change data.process gerrit changes.resolve CL deps.details pigweed:123456",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@4@@@",
+      "@@@STEP_LOG_LINE@json.output@{@@@",
+      "@@@STEP_LOG_LINE@json.output@  \"current_revision\": \"HASH\", @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"project\": \"project\", @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"revisions\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@    \"HASH\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@      \"_number\": 1, @@@",
+      "@@@STEP_LOG_LINE@json.output@      \"commit\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@        \"message\": \"\"@@@",
+      "@@@STEP_LOG_LINE@json.output@      }@@@",
+      "@@@STEP_LOG_LINE@json.output@    }@@@",
+      "@@@STEP_LOG_LINE@json.output@  }, @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"status\": \"NEW\"@@@",
+      "@@@STEP_LOG_LINE@json.output@}@@@",
+      "@@@STEP_LOG_END@json.output@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "checkout pigweed.change data.process gerrit changes.resolve CL deps.resolve deps for pigweed:123456",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@4@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "checkout pigweed.change data.process gerrit changes.resolve CL deps.pass",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@4@@@"
+    ]
+  },
+  {
+    "cmd": [],
     "name": "checkout pigweed.change data.changes",
     "~followup_annotations": [
       "@@@STEP_NEST_LEVEL@2@@@"
diff --git a/recipes/xrefs.expected/tryjob.json b/recipes/xrefs.expected/tryjob.json
index ec550b8..ccd633a 100644
--- a/recipes/xrefs.expected/tryjob.json
+++ b/recipes/xrefs.expected/tryjob.json
@@ -101,6 +101,57 @@
   },
   {
     "cmd": [],
+    "name": "checkout pigweed.change data.process gerrit changes.resolve CL deps",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@3@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "[CACHE]/cipd/infra/tools/luci/gerrit/latest/gerrit",
+      "change-detail",
+      "-host",
+      "https://pigweed-review.googlesource.com",
+      "-input",
+      "{\"change_id\": \"123456\", \"params\": {\"o\": [\"CURRENT_COMMIT\", \"CURRENT_REVISION\"]}}",
+      "-output",
+      "/path/to/tmp/json"
+    ],
+    "name": "checkout pigweed.change data.process gerrit changes.resolve CL deps.details pigweed:123456",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@4@@@",
+      "@@@STEP_LOG_LINE@json.output@{@@@",
+      "@@@STEP_LOG_LINE@json.output@  \"current_revision\": \"HASH\", @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"project\": \"project\", @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"revisions\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@    \"HASH\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@      \"_number\": 1, @@@",
+      "@@@STEP_LOG_LINE@json.output@      \"commit\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@        \"message\": \"\"@@@",
+      "@@@STEP_LOG_LINE@json.output@      }@@@",
+      "@@@STEP_LOG_LINE@json.output@    }@@@",
+      "@@@STEP_LOG_LINE@json.output@  }, @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"status\": \"NEW\"@@@",
+      "@@@STEP_LOG_LINE@json.output@}@@@",
+      "@@@STEP_LOG_END@json.output@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "checkout pigweed.change data.process gerrit changes.resolve CL deps.resolve deps for pigweed:123456",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@4@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "checkout pigweed.change data.process gerrit changes.resolve CL deps.pass",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@4@@@"
+    ]
+  },
+  {
+    "cmd": [],
     "name": "checkout pigweed.change data.changes",
     "~followup_annotations": [
       "@@@STEP_NEST_LEVEL@2@@@"