From 300497f7950e6358c28d89c99553ce8589ab041a Mon Sep 17 00:00:00 2001 From: Imani Manyara Date: Mon, 2 Nov 2026 10:33:16 -0500 Subject: [PATCH] fix(dependabot): call the generator's write without --strict agent-kit's generator refuses --strict beside --write with exit 2, so every drifting repository was recorded as skipped:write-exit-2 and the run still passed. Write with check --write, and count a failed write as an error so the run fails. --- dependabot/reconcile.py | 7 ++++++- dependabot/tests/test_reconcile.py | 32 +++++++++++++++++++++++++++++- 2 files changed, 37 insertions(+), 2 deletions(-) diff --git a/dependabot/reconcile.py b/dependabot/reconcile.py index 07bf16b..e5ea09b 100644 --- a/dependabot/reconcile.py +++ b/dependabot/reconcile.py @@ -133,7 +133,10 @@ def reconcile_repo(repo, owner, gh, shell, opts, policy, workdir): if rc != 1: return f"skipped:generator-exit-{rc}", "the generator could not render this file safely" - rc = shell.run(gen + ["--write"], check=False).returncode + # `--strict` is check-only: the generator refuses it beside `--write` with exit 2, which + # silently skipped every repository from the day agent-kit started enforcing that. + write = [sys.executable, opts.generator_script, "check", dest, "--write", "--policy", opts.policy] + rc = shell.run(write, check=False).returncode if rc not in (0, 1): return f"skipped:write-exit-{rc}", "" changed = changed_paths(shell, dest) @@ -209,6 +212,8 @@ def main(argv=None, gh=None, shell=None): except Exception as exc: # one repository's failure must not stop the others errors += 1 status, note = "error", str(exc)[:300] + if status.startswith("skipped:write-exit-"): + errors += 1 # the generator failed to write: a tool error, not a decision to skip rows.append((repo["full_name"], status, note)) finally: shutil.rmtree(workdir, ignore_errors=True) diff --git a/dependabot/tests/test_reconcile.py b/dependabot/tests/test_reconcile.py index 2acf934..62e254e 100644 --- a/dependabot/tests/test_reconcile.py +++ b/dependabot/tests/test_reconcile.py @@ -49,7 +49,10 @@ def run(self, cmd, check=True, input_text=None): if "clone" in cmd: os.makedirs(cmd[-1]) return subprocess.CompletedProcess(cmd, 0, "", "") - if "check" in cmd and "--strict" in cmd: + if "check" in cmd: + if "--strict" in cmd and "--write" in cmd: + # the real generator: "--strict goes with `check`, without --write" + return subprocess.CompletedProcess(cmd, 2, "", "") dest = cmd[cmd.index("check") + 1] name = os.path.basename(dest).split("__", 1)[1] if "--write" in cmd: @@ -161,6 +164,33 @@ def test_apply_pushes_only_the_reconciler_branch(self): create = next(r for r in gh.runs if r[1] == "create") self.assertIn("Adopt the Dependabot standard", create) + def test_write_never_carries_strict(self): + # The generator refuses `--strict` beside `--write` (exit 2); on 2026-11-02 that made every + # repository "skipped:write-exit-2" while the run still reported success. + code, _, shell = self.run_main(self.base_routes(), self.RC, "--apply") + self.assertEqual(code, 0) + writes = [c for c in shell.cmds if "--write" in c] + self.assertTrue(writes, "drifting repositories are written") + self.assertFalse([c for c in writes if "--strict" in c]) + + def test_a_failed_write_fails_the_run(self): + class WriteFails(FakeShell): + def run(self, cmd, check=True, input_text=None): + if "--write" in cmd: + self.cmds.append(cmd) + return subprocess.CompletedProcess(cmd, 3, "", "") + return super().run(cmd, check, input_text) + captured = [] + orig = common.write_summary + common.write_summary = captured.append + try: + code = reconcile.main(["--owner", "simtabi", "--generator", self.gen], + gh=FakeGh(self.base_routes()), shell=WriteFails(self.RC)) + finally: + common.write_summary = orig + self.assertEqual(code, 1) + self.assertRegex(captured[0], r"\| simtabi/drifting \| skipped:write-exit-3 \|") + def test_error_in_one_repo_is_reported_and_exits_nonzero(self): routes = self.base_routes() routes = {"repos/simtabi/drifting/pulls?": common.GhError("boom"), **routes}