fix(job-key): report a non-object state file in --audit instead of a traceback (#626)

doc.get("seen", doc) raised AttributeError on a top-level array, and
entry.get("url") raised it on an entry that was not an object, so the
existing "expected an object of job entries" message was never reached.
Guard both and exit 1 with a one-line message.

Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
Rudy Mizrahi CelekliandClaude Sonnet 5.5 authored and GitHub committed 2026-10-18 08:14:09 +02:00
1 parent 1bb3bee2f2
commit 9c802a4aa2
3 files changed
+32 -1

No files matched your search

+5
View File
@@ -68,6 +68,11 @@ per-file diff commands.
### Fixed ### Fixed
- **`job_key.py --audit` reports a state file that is not an object of entries** (`tools/job_key.py`,
`tests/test_job_key.py`) - a top-level JSON array, or an entry that is a string or number, crashed the
audit with an `AttributeError` traceback on `.get`, although the function already had a one-line error
for an unusable `seen` value. Both now print a one-line message naming the problem and exit 1.
- **`freehire-search` emits the contract's `YYYY-MM-DD` date instead of the API's ISO timestamp** - **`freehire-search` emits the contract's `YYYY-MM-DD` date instead of the API's ISO timestamp**
(`.agents/skills/freehire-search/cli/src/helpers.ts`, `tests/parsing.test.ts`, (`.agents/skills/freehire-search/cli/src/helpers.ts`, `tests/parsing.test.ts`,
`tests/commands.test.ts`, `SKILL.md`) - `toResult` passed `posted_at` through verbatim, so `tests/commands.test.ts`, `SKILL.md`) - `toResult` passed `posted_at` through verbatim, so
+22
View File
@@ -256,6 +256,28 @@ class AuditCLI(unittest.TestCase):
self.assertEqual(code, 0) self.assertEqual(code, 0)
self.assertIn("acme_soc-analyst_athens", report["legacy_three_part_keys"]) self.assertIn("acme_soc-analyst_athens", report["legacy_three_part_keys"])
def run_audit_raw(self, document) -> subprocess.CompletedProcess:
import tempfile
with tempfile.NamedTemporaryFile("w", suffix=".json", delete=False) as fh:
json.dump(document, fh)
path = fh.name
return subprocess.run(
[sys.executable, str(TOOL), "--audit", path], capture_output=True, text=True, encoding="utf-8"
)
def test_non_object_state_file_is_reported_not_a_traceback(self):
proc = self.run_audit_raw([])
self.assertEqual(proc.returncode, 1)
self.assertIn("expected an object of job entries", proc.stderr)
self.assertNotIn("Traceback", proc.stderr)
def test_non_object_entry_is_reported_not_a_traceback(self):
proc = self.run_audit_raw({"seen": {"acme_soc-analyst": "oops"}})
self.assertEqual(proc.returncode, 1)
self.assertIn("entry is not an object: acme_soc-analyst", proc.stderr)
self.assertNotIn("Traceback", proc.stderr)
if __name__ == "__main__": if __name__ == "__main__":
unittest.main() unittest.main()
+5 -1
View File
@@ -183,10 +183,14 @@ def audit(path: Path) -> int:
except (OSError, json.JSONDecodeError) as exc: except (OSError, json.JSONDecodeError) as exc:
print(f"cannot read {path}: {exc}", file=sys.stderr) print(f"cannot read {path}: {exc}", file=sys.stderr)
return 1 return 1
seen = doc.get("seen", doc) seen = doc.get("seen", doc) if isinstance(doc, dict) else doc
if not isinstance(seen, dict): if not isinstance(seen, dict):
print(f"{path}: expected an object of job entries", file=sys.stderr) print(f"{path}: expected an object of job entries", file=sys.stderr)
return 1 return 1
not_objects = [k for k, v in seen.items() if not isinstance(v, dict)]
if not_objects:
print(f"{path}: entry is not an object: {not_objects[0]}", file=sys.stderr)
return 1
malformed = [k for k in seen if not is_canonical(k) and not is_legacy_shape(k)] 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)] legacy = [k for k in seen if is_legacy_shape(k)]