fix(pilot): strip opencode $schema + defensive harvest dict-strip

- opencode_review.install_config: opencode 1.3.10 rejects factory
  opencode.json$'\''s top-level "$schema" key with 'Unrecognized key'
  at parse time, causing every fresh review to fail as the misleading
  'opencode empty text (rc=0)'. Add a small drop-list of known-bad
  top-level keys + a sanitizer applied to both the fast (no-env) and
  slow (env-substituted) write paths.

- feedback_harvest: Gitea versions occasionally serialize reaction
  content / inline-comment resolver as a dict instead of a string,
  crashing harvester with 'dict object has no attribute strip'. Coerce
  both via str() with a brief comment documenting the WHY.

Affected reviews: techspark/suaspark-dashboard #11 (fixed),
PRAgent reviews on PR #9 era (recovered).
Verified: opencode 1.3.10 emits real '### Summary of Changes' body.
This commit is contained in:
Marcos Paulo
2026-08-24 19:38:48 +00:00
parent 0d73a075cd
commit 317942e15b
2 changed files with 39 additions and 8 deletions
+27 -4
View File
@@ -421,9 +421,33 @@ def install_config(src: str, dst: str) -> bool:
return False
default_url = os.environ.get("PRAGENT_MODEL_BASE_URL", "").strip()
default_key = os.environ.get("PRAGENT_MODEL_API_KEY", "").strip()
# Strip keys opencode's runtime rejects on every version bump we touch. The
# factory `opencode.json` is committed for documentation (so `$schema`
# stays in the file for editor IntelliSense), but opencode 1.3.10 errors
# with "Unrecognized key: schema" at config-parse time and refuses to
# register ANY provider/model — surfacing to the user as the misleading
# "opencode empty text (rc=0)" failure post. Keep the drop list small and
# documented; smoke-test before adding more.
_OPENCODE_INCOMPATIBLE_TOP_KEYS = ("$schema",)
def _sanitize_and_write(cfg: dict) -> None:
for k in _OPENCODE_INCOMPATIBLE_TOP_KEYS:
cfg.pop(k, None)
with open(dst, "w", encoding="utf-8") as f:
json.dump(cfg, f, indent=2)
if not default_url and not default_key:
# Fast path: no env at all → commit copy is fine, no rewrite needed.
shutil.copy2(src, dst)
# Fast path: no env at all → still sanitize (the schema key would
# poison every fresh-pod warm-up if we skipped).
try:
with open(src, encoding="utf-8") as f:
cfg = json.load(f)
_sanitize_and_write(cfg)
except (OSError, ValueError):
# If we can't parse, fall back to verbatim copy — opencode will
# report the parse error itself, no need to hide it.
shutil.copy2(src, dst)
return True
try:
with open(src, encoding="utf-8") as f:
@@ -439,8 +463,7 @@ def install_config(src: str, dst: str) -> bool:
prov["options"]["baseURL"] = url
if key:
prov["options"]["apiKey"] = key
with open(dst, "w", encoding="utf-8") as f:
json.dump(cfg, f, indent=2)
_sanitize_and_write(cfg)
except (OSError, ValueError, AttributeError):
# A malformed config is opencode's problem to report, not ours to hide.
shutil.copy2(src, dst)