affaan-m/ECC · error · ValueError
duplicate JSON field
Error message
duplicate JSON field
What it means
The library parses JSON with a custom object_pairs_hook that rejects any object containing the same key twice, raising "duplicate JSON field". Standard json.loads silently keeps only the last duplicate, which would make hash-bound bundles ambiguous, so duplicates are treated as invalid input.
Solutions
- Open the file and find the duplicated key in the offending object (JSON parsers don't report its location — try a strict parser like `python -c "import json,sys; json.load(open(sys.argv[1]), object_pairs_hook=lambda p: [p])"` style checks or jq with duplicate detection) and delete one occurrence.
- Regenerate the file from its source tool instead of hand-merging.
- Run a pre-check with a strict parser that flags duplicates before handing the path to the library.
Example fix
// before
{"timeline_name": "main", "timeline_name": "main_v2"}
// after
{"timeline_name": "main_v2"} Defensive patterns
Strategy: validation
Validate before calling
import json, sys
def reject_duplicate_keys(path):
def hook(pairs):
keys = [k for k, _ in pairs]
if len(keys) != len(set(keys)):
raise ValueError(f"{path}: duplicate JSON field {set(k for k in keys if keys.count(k) > 1)}")
return dict(pairs)
with open(path, encoding="utf-8") as f:
json.load(f, object_pairs_hook=hook) Try / catch
try:
request = load_application_request(path)
except ValueError as exc:
if "duplicate JSON field" in str(exc):
print(f"{path}: merge edits carefully; remove repeated keys")
raise Prevention
- Never hand-merge JSON config blocks; use a merging tool that dedupes.
- Run a duplicate-key linter (e.g. json.tool with a strict hook) in CI.
- Generate configs from a single source of truth.
- Avoid copy-pasting JSON fragments between files.
When it happens
Trigger: Any JSON artifact parsed by this module (request file, snapshot, receipt, approval file) contains an object literal with the same key appearing two or more times, e.g. {"fps": {"numerator": 24000, "numerator": 24000, ...}}.
Common situations: Hand-merged config files where two edits both added the same key; template concatenation; a generator bug emitting a key twice; copy-pasted JSON blocks.
Understand the failure class
Background: JSON parse error: "Unexpected token" / "not valid JSON" / "failed to parse" — what JSON parsers are really complaining about — this error's family across 45 libraries.
Related errors
- application request must be a JSON object
- bundle values must be finite JSON data
- candidate configuration exceeds 1 MiB
- gate.taskset_invalid
- input_artifacts must be a list of local paths
AI-assisted analysis of affaan-m/ECC@8321021c54 (2026-09-16).
Data as JSON: /api/errors/64270df022f21577.
Report an issue: GitHub.
Appendix: source
Thrown at skills/taste-application/scripts/tasteforge/integration.py:175
os.close(descriptor)
if parent is not None:
os.close(parent)
def load_application_request(path: str | Path) -> dict:
"""Load only a bounded resident request; never follow a config symlink."""
value = _read_local(str(Path(path).absolute()), parse_json=True)
if not isinstance(value, dict):
raise ValueError("application request must be a JSON object")
return value
def _load_json(data: bytes) -> Any:
def unique(pairs):
result = {}
for key, value in pairs:
if key in result:
raise ValueError("duplicate JSON field")
result[key] = value
return result
try:
value = json.loads(data, object_pairs_hook=unique)
_canonical(value)
return value
except (UnicodeError, RecursionError) as exc:
raise ValueError("invalid JSON artifact") from exc
def _snapshot(baseline: dict) -> dict:
_object(baseline, {"project_file", "snapshot_file", "project_name", "timeline_name",
"fps", "timeline_range"})
_rate(baseline["fps"])
bounds = _range(baseline["timeline_range"])
_artifact(baseline["project_file"])
snapshot = _artifact(baseline["snapshot_file"], parse_json=True)View on GitHub (pinned to 8321021c54)