Repository navigation
Description
Summary
scripts/sync_research_workflow.py parses three JSON documents with json.loads and then uses the results as dicts with no shape validation: the target's upstream manifest (.agents/research-workflow.upstream.json), and the source/target repository profiles (.agents/research-workflow.json). A document that is syntactically valid JSON but the wrong shape — a top-level array, a files value that is a list or string, a numeric workflow_docs, a non-string lifecycle_skills element — raises TypeError/AttributeError, which main() does not catch (it catches only ValueError, OSError, KeyError, subprocess.CalledProcessError at :173). The tool therefore dies with a raw traceback naming Python internals instead of its own contract: a one-line diagnostic on stderr and exit 1.
One shape is actively misleading rather than crashing: a string lifecycle_skills is iterated character by character, so the tool reports missing source directory: .agents/skills/r — blaming a directory that was never configured.
Evidence (main c90fcbc7, script md5 319a3caa2fdbf117b4ba2a3867dfdf1d, identical in the sandbox clone and the laptop primary)
Laptop probe (~/git/fleet/probe-2026-10-30-laneB-syncshape/probe.py, real module, fixture repos built exactly like scripts/tests/test_sync_research_workflow.py):
| Case | Result |
|---|---|
control: valid fixture, --check after a --write |
returns cleanly |
| manifest with a JSON syntax error | JSONDecodeError (a ValueError) — caught by main, clean error + exit 1 ✅ |
manifest = [] (top-level array) |
TypeError: list indices must be integers or slices, not str — not caught |
manifest = {"files": ["x"]} |
AttributeError: 'list' object has no attribute 'keys' (:112) — not caught |
manifest = {"files": "abc"} |
AttributeError: 'str' object has no attribute 'keys' — not caught |
source profile = [] |
AttributeError: 'list' object has no attribute 'get' (:46) — not caught |
source profile workflow_docs: 123 |
TypeError: argument should be a str or an os.PathLike… not 'int' (:47) — not caught |
source profile lifecycle_skills: [123] |
TypeError: unsupported operand type(s) for /: 'PosixPath' and 'int' — not caught |
source profile lifecycle_skills: "research-workflow" (string) |
misleading ValueError: missing source directory: .agents/skills/r (iterates the string's characters) |
Relevant code: profiles loaded at scripts/sync_research_workflow.py:44-47; manifest loaded at :96-99 and used as old["files"].get(...) / old["files"].keys() at :109, :112, :122; the catch tuple at :173 omits TypeError and AttributeError.
Why it matters
This script is the guard that keeps the mirrored research-workflow copy honest: it is supposed to refuse bad states with a readable reason ("local edits need reconciliation", "workflow mirror differs"). The manifest and profiles are exactly the files that get hand-edited, truncated by a bad merge, or regenerated by another tool — the moment the guard is most needed is the moment its input is malformed. Today that moment produces a traceback that points at PosixPath arithmetic instead of at the offending file and field, and the string-lifecycle_skills case sends the operator hunting for a directory named r. Sibling tool scripts/research_workflow.py already validates its profile shapes with isinstance checks (profile() at :77-102); the sync tool does not.
Acceptance criteria
- A parseable-but-wrong-shaped manifest or profile (top level not an object;
filesnot an object of string digests;workflow_docsnot a string;lifecycle_skillsnot a list of strings) fails with a one-line diagnostic naming the file and the offending field, on stderr, exit 1 — no traceback. - A string
lifecycle_skillsis rejected as a shape error, never iterated as characters. - Valid fixtures behave exactly as before: the four existing tests in
scripts/tests/test_sync_research_workflow.pystill pass. - New tests cover at least: top-level-array manifest, non-object
files, non-object profile, and stringlifecycle_skills. - Scope is validation at the load sites only — no change to the sync/check semantics, the manifest format, or the local-edit protection rules.
Activity
dezren39 commentedon Oct 30, 2026
🤖 Agent note · filed
12 remaining items
dezren39 commentedon Nov 1, 2026
🤖 Agent note · picked up
dezren39 commentedon Nov 1, 2026
🤖 Agent note · done
Merge pull request #3700 from developing-today/queue/4365-2800-sync-r…