fix(skills): support opencode backend (unknown backend "opencode")#147
Conversation
The opencode backend (1.20.8, #141) routes skill install through the generic InstallSkillsReport(result.Backend) call in the /orch picker, but the skills materializer only knew about "cc" and "codex". Activating opencode crashed with: skills: unknown backend "opencode". Add opencode as a first-class skills backend: - BackendOpenCode writes to ~/.config/opencode/skills/<name>/SKILL.md (opencode's global discovery dir), rendering name+description only — opencode ignores allowed-tools and has no openai.yaml sibling. - main.go: sync skills on every opencode activation (idempotent), matching the cc/codex startup sync. opencode has no global-install prompt, so this is unconditional. - InstallSkillsBoth -> InstallSkillsAll (now three backends); /skills status gains an "oc" column. - Tests: opencode materialization + a guard that all catalog names satisfy opencode's stricter name regex (no underscores).
Sigilix OverviewEffort: 2/5 (small) Quality gates
Summary — latest pushAdds first-class opencode backend support to the skills materializer so /orch activation no longer crashes with 'unknown backend', and wires unconditional skill sync on opencode activation since it bypasses the global-install consent prompt. The rename from InstallSkillsBoth to InstallSkillsAll reflects the expansion to three backends, and the /skills status command gains an opencode column. Existing cc/codex paths are untouched, making this a low-risk additive change. Important files
Confidence: 4/5The change is an additive backend extension with no modifications to existing cc/codex paths, and the unconditional sync is correctly scoped to opencode's known consent-bypass behavior.
Suggested labels:
|
✅ QualityMax Pipeline
Powered by QualityMax — AI-Powered Test Automation |
There was a problem hiding this comment.
1 finding outside the diff
| File | Scope | Finding |
|---|---|---|
| internal/skills/materialize.go:45 | file-scope | Missing test for unknown backend error path in SkillsDir |
| // Skills sync every launch (idempotent). opencode has no global-install | ||
| // prompt — qmax manages its config directly — so this is unconditional, | ||
| // unlike the OrchGlobalInstall-gated cc/codex sync above. | ||
| _, _ = setup.InstallSkills("opencode") |
There was a problem hiding this comment.
No test for opencode skills sync in main startup
The PR adds an unconditional skills sync for opencode backend at line 443 (_, _ = setup.InstallSkills("opencode")). This is a new runtime behavior that is not covered by any test in the diff. The existing tests for InstallSkills do not verify that opencode backend triggers the sync correctly.
More Info
- Threat model: If the sync fails silently (due to the ignored error), opencode users may not receive the skill catalog, leading to missing functionality. The lack of a test means regressions in the integration between main startup and the skills materialization could go unnoticed.
- Specific code citations: Line 443 in main.go:
_, _ = setup.InstallSkills("opencode"). - Existing protections: The
InstallSkillsfunction is tested via the materialize unit tests, but there is no integration test that exercises the opencode backend activation path in main.go. - Proposed mitigation: Add an integration test that simulates opencode backend activation and verifies skills are materialized, or at least ensure the
InstallSkillscall is covered in existing test suites for the opencode backend. - Alternative mitigations considered: Assume the unit tests for
Materializeare sufficient, but they do not cover the call site in main.go or the error handling (ignored error). - Severity calibration: Score 3 because the behavior is idempotent and additive; a failure would degrade user experience but not break core functionality. However, it's a new runtime path that should be tested.
Prompt To Fix With AI
This is a comment left during a code review.
Path: main.go
Line: 446
Comment:
**No test for opencode skills sync in main startup**
The PR adds an unconditional skills sync for opencode backend at line 443 (`_, _ = setup.InstallSkills("opencode")`). This is a new runtime behavior that is not covered by any test in the diff. The existing tests for `InstallSkills` do not verify that opencode backend triggers the sync correctly.
Threat model:
If the sync fails silently (due to the ignored error), opencode users may not receive the skill catalog, leading to missing functionality. The lack of a test means regressions in the integration between main startup and the skills materialization could go unnoticed.
Specific code citations:
Line 443 in main.go: `_, _ = setup.InstallSkills("opencode")`.
Existing protections:
The `InstallSkills` function is tested via the materialize unit tests, but there is no integration test that exercises the opencode backend activation path in main.go.
Proposed mitigation:
Add an integration test that simulates opencode backend activation and verifies skills are materialized, or at least ensure the `InstallSkills` call is covered in existing test suites for the opencode backend.
Alternative mitigations considered:
Assume the unit tests for `Materialize` are sufficient, but they do not cover the call site in main.go or the error handling (ignored error).
Severity calibration:
Score 3 because the behavior is idempotent and additive; a failure would degrade user experience but not break core functionality. However, it's a new runtime path that should be tested.
How can I resolve this? If you propose a fix, please make it concise.
| case BackendOpenCode: | ||
| return filepath.Join(home, ".config", "opencode", "skills"), nil | ||
| default: | ||
| return "", fmt.Errorf("skills: unknown backend %q", backend) |
There was a problem hiding this comment.
New backend constant expands attack surface for path traversal validation
Adding BackendOpenCode introduces a new path ~/.config/opencode/skills that Materialize will write to. The existing ensureWithinHome guard and mkdirStrict permissions (0600/0700) still apply, but any future changes to path validation or symlink checks must consider this new backend. The risk is low because the same security controls are reused, but the expansion increases the code surface that must remain consistent.
Remediation: Resolve the path and confirm it stays within an allowed base directory (reject .. and absolute paths); prefer an allow-list of known-safe names over user-supplied paths.
More Info
- Threat model: An attacker could attempt to exploit a symlink or path traversal if the validation logic is later modified or bypassed for the opencode backend. The impact is local file write outside the intended directory.
- Specific code citations:
SkillsDircase forBackendOpenCodereturnsfilepath.Join(home, ".config", "opencode", "skills").MaterializecallsensureWithinHomeandmkdirStrict. - Existing protections:
ensureWithinHomeresolves symlinks and checks that the final path is within the user's home directory.mkdirStrictsets owner-only permissions. - Proposed mitigation: Ensure any future changes to path validation or directory creation apply uniformly to all backends, including opencode.
- Alternative mitigations considered: Consider adding a backend-specific validation hook, but the current uniform approach is sufficient.
- Severity calibration: Score 2 because it's a hardening gap that widens the attack surface slightly; the existing guards are robust and the new backend follows the same pattern.
Prompt To Fix With AI
This is a comment left during a code review.
Path: internal/skills/materialize.go
Line: 45
Comment:
**New backend constant expands attack surface for path traversal validation**
Adding `BackendOpenCode` introduces a new path `~/.config/opencode/skills` that `Materialize` will write to. The existing `ensureWithinHome` guard and `mkdirStrict` permissions (0600/0700) still apply, but any future changes to path validation or symlink checks must consider this new backend. The risk is low because the same security controls are reused, but the expansion increases the code surface that must remain consistent.
Threat model:
An attacker could attempt to exploit a symlink or path traversal if the validation logic is later modified or bypassed for the opencode backend. The impact is local file write outside the intended directory.
Specific code citations:
`SkillsDir` case for `BackendOpenCode` returns `filepath.Join(home, ".config", "opencode", "skills")`. `Materialize` calls `ensureWithinHome` and `mkdirStrict`.
Existing protections:
`ensureWithinHome` resolves symlinks and checks that the final path is within the user's home directory. `mkdirStrict` sets owner-only permissions.
Proposed mitigation:
Ensure any future changes to path validation or directory creation apply uniformly to all backends, including opencode.
Alternative mitigations considered:
Consider adding a backend-specific validation hook, but the current uniform approach is sufficient.
Severity calibration:
Score 2 because it's a hardening gap that widens the attack surface slightly; the existing guards are robust and the new backend follows the same pattern.
How can I resolve this? If you propose a fix, please make it concise.
…balInstall
The /orch picker gated InstallSkillsReport behind consent.GlobalInstall,
which opencode never sets (its consent flow skips the global-install
prompt). So opencode-only users activating via /orch got no skills, even
though WriteOpenCodeConfig already ran unconditionally in the same flow
and main.go syncs opencode skills on every startup.
Decouple the skills install from the GlobalInstall gate for opencode only:
cc/codex keep their explicit opt-in; opencode matches its managed-config
model (and main.go). RunOrch stays gated — IsOrchInstalled("opencode")
already returns true, so it never runs for opencode anyway.
…ves; surface startup error Address review (sigilix): - TestSkillsDirRejectsUnknownBackend: assert invalid backends error and that Materialize propagates it (guards the default branch). - TestOpenCodeNameRegexRejectsInvalid: underscores, leading/trailing/double hyphens, uppercase, empty all rejected — confirms the regex itself. - main.go: log the opencode InstallSkills error to stderr instead of discarding it, matching the adjacent WriteOpenCodeConfig warning.
Problem
Activating the opencode backend (shipped in 1.20.8 / #141) crashed skill install with:
The
/orchpicker passes the selected backend into the genericsetup.InstallSkillsReport(result.Backend)call (repl.go:537), but the skills materializer only knew aboutccandcodex— the opencode backend was added without wiring its skill format.Root cause
internal/skills/materialize.goSkillsDir/renderSkillMDhad noopencodecase, soMaterialize("opencode", home)hit theunknown backenderror path. opencode genuinely supports skills (docs:~/.config/opencode/skills/<name>/SKILL.md), so the fix is to make it a first-class backend rather than silently skip.Fix
BackendOpenCode→ writes~/.config/opencode/skills/<name>/SKILL.md, frontmattername+descriptiononly (opencode ignoresallowed-tools; noopenai.yamlsibling).InstallSkillsBoth→InstallSkillsAll(now three backends);/skillsstatus gains anoccolumn.TestMaterializeOpenCode(dir, frontmatter, no allowed-tools, no openai.yaml) +TestCatalogNamesAreOpenCodeCompatible(all 27 names satisfy opencode's stricter regex^[a-z0-9]+(-[a-z0-9]+)*).Verification
go build ./...✅go test ./internal/skills/ ./internal/setup/ ./internal/repl/✅ (all pass, incl. 2 new tests)Notes / coverage
~/.claude/skills, so dual cc+opencode users already had skills via the Claude-compatible path. This native materialization ensures opencode-only users get the catalog too, and keeps it authoritative in opencode's own dir.Risk
LOW — additive backend; existing cc/codex paths unchanged. The one behavioral change is that
/skills installnow also writes to~/.config/opencode/skills/(previously only cc+codex).