From 3c97e7bc93f20c8edf0c03c0b607781488b3d6b5 Mon Sep 17 00:00:00 2001 From: Rudy Mizrahi Celekli <47457359+rudycelekli@users.noreply.github.com> Date: Mon, 19 Oct 2026 13:30:01 -0400 Subject: [PATCH] fix(job-key): report a non-string url in --audit instead of a traceback (#634) * fix(job-key): report a non-string url in --audit instead of a traceback A numeric, list or object url crashed the duplicate-URL pass with an AttributeError on .rstrip. Report it with a one-line error naming the key, as the other malformed-state branches do. Co-Authored-By: Claude Sonnet 5.5 * changelog: place the #634 entry under [Unreleased] Co-Authored-By: Claude Opus 5.5 --------- Co-authored-by: Claude Sonnet 5.5 Co-authored-by: Mads Lorentzen --- CHANGELOG.md | 5 +++++ tests/test_job_key.py | 8 ++++++++ tools/job_key.py | 4 ++++ 3 files changed, 17 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 17b313e1..284f8a7d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -72,6 +72,11 @@ per-file diff commands. `--country`. Results, stdout and the exit code are unchanged, so a bucket freehire adds later still works. +- **`job_key.py --audit` reports an entry whose `url` is not a string** (#634, `tools/job_key.py`, + `tests/test_job_key.py`) - a numeric, list or object `url` crashed the duplicate-URL pass with an + `AttributeError` traceback on `.rstrip`, although the audit already reports other malformed state + with a one-line error and exit 1. It now names the offending key the same way. + ## [1.7.3] - 2026-10-19 ### Added diff --git a/tests/test_job_key.py b/tests/test_job_key.py index 543bc70b..e7f82b0d 100644 --- a/tests/test_job_key.py +++ b/tests/test_job_key.py @@ -278,6 +278,14 @@ class AuditCLI(unittest.TestCase): self.assertIn("entry is not an object: acme_soc-analyst", proc.stderr) self.assertNotIn("Traceback", proc.stderr) + def test_non_string_url_is_reported_not_a_traceback(self): + for bad in (123, ["https://example.com/job"], {"href": "x"}): + with self.subTest(url=bad): + proc = self.run_audit_raw({"seen": {"acme_soc-analyst": {"company": "Acme", "title": "SOC Analyst", "url": bad}}}) + self.assertEqual(proc.returncode, 1) + self.assertIn("entry url is not a string: acme_soc-analyst", proc.stderr) + self.assertNotIn("Traceback", proc.stderr) + if __name__ == "__main__": unittest.main() diff --git a/tools/job_key.py b/tools/job_key.py index 377c5fe9..babc887b 100644 --- a/tools/job_key.py +++ b/tools/job_key.py @@ -191,6 +191,10 @@ def audit(path: Path) -> int: if not_objects: print(f"{path}: entry is not an object: {not_objects[0]}", file=sys.stderr) return 1 + bad_url = [k for k, v in seen.items() if v.get("url") is not None and not isinstance(v["url"], str)] + if bad_url: + print(f"{path}: entry url is not a string: {bad_url[0]}", file=sys.stderr) + return 1 malformed = [k for k in seen if not is_canonical(k) and not is_legacy_shape(k)] legacy = [k for k in seen if is_legacy_shape(k)]