Skip to content

feat(skill): built-in opencode-meta skill#26617

Merged
kitlangton merged 4 commits into
anomalyco:devfrom
kitlangton:kit/opencode-meta-skill
May 10, 2026
Merged

feat(skill): built-in opencode-meta skill#26617
kitlangton merged 4 commits into
anomalyco:devfrom
kitlangton:kit/opencode-meta-skill

Conversation

@kitlangton
Copy link
Copy Markdown
Contributor

@kitlangton kitlangton commented May 10, 2026

Why

Users keep hitting ConfigInvalidError startup crashes because the model writes invalid opencode.json shapes when asked to edit opencode itself. opencode hard-fails on invalid config by design, so a hallucinated field name produces a cryptic error and a stuck session. Recent Discord example: a skills: field written as an array of {name, path} objects instead of {paths: [...]}.

This PR ships a built-in skill, customize-opencode, that documents the actual schemas. The skill is registered before disk discovery so a user can shadow it by dropping a same-named SKILL.md.

Rollout: gated, default-on for unstable channels

Behind OPENCODE_EXPERIMENTAL_CUSTOMIZE_SKILL. Defaults:

  • dev, beta, local: ON. Internal users dogfood it.
  • prod, latest: OFF. Stable users opt in.
  • OPENCODE_EXPERIMENTAL_CUSTOMIZE_SKILL=false forces off, =true forces on.

This mirrors the rollout pattern used by OPENCODE_EXPERIMENTAL_HTTPAPI before it graduated. Factored into a small unstableDefault() helper in flag.ts so future experiments with the same shape can reuse it.

What's in the skill

V1 surface only (Dax's V2 work is not described):

  • opencode.json top-level shape with the only accepted forms
  • A short positive list of shape notes (e.g. "skills is an object with paths and/or urls, not an array") so the model gets the disambiguation it needs without a wrong/right table
  • Where every config / agent / skill file lives (project vs global)
  • Skill loader requirements (folder-named-after-skill containing SKILL.md, frontmatter rules)
  • Agent definition, both inline and file form, allowed mode/frontmatter fields
  • Plugin authoring (npm spec / file path / tuple form, hook surface, the function-not-object requirement)
  • MCP server discrimination by type, the command: [] requirement
  • Permission shape, action values, insertion-order semantics
  • Escape hatches (OPENCODE_DISABLE_PROJECT_CONFIG etc.) for when a user's config is broken and opencode won't start

Deprecated surfaces are not documented (the mode: alias for agent:, the per-agent tools: boolean map).

Trigger description

Gated narrowly using the patterns Anthropic and the community have settled on (front-loaded keywords, Use ONLY when…, explicit negative clause):

Use ONLY when the user is editing or creating opencode's own configuration: opencode.json, opencode.jsonc, files under .opencode/, or files under ~/.config/opencode/. Also use when creating or fixing opencode agents, subagents, skills, plugins, MCP servers, or permission rules. Do not use for the user's own application code, or for any project that is not configuring opencode itself.

Goal: the skill stays quiet when the user is doing normal coding work in their own project.

How it's wired

  • Built-in skill registered before disk scan in Skill.state, so user-disk skills with the same name override it.
  • Body imported as text via import CUSTOMIZE_OPENCODE_SKILL_BODY from \"./prompt/customize-opencode.md\" with { type: \"text\" }. Adds an ambient *.md declaration in packages/opencode/src/markdown.d.ts for tsgo.
  • Stored at packages/opencode/src/skill/prompt/customize-opencode.md, mirroring how built-in agent prompts (agent/prompt/scout.txt etc.) are organized.

Disable mechanism

  • Stable channels: simply don't set the env var.
  • Unstable channels: OPENCODE_EXPERIMENTAL_CUSTOMIZE_SKILL=false.
  • Any channel: shadow on disk with a same-named SKILL.md (built-in registers first, disk discovery overwrites).

There is no config-level disable. Skills as a category have no skill: { <name>: { disable: true } } mechanism today (only agents, mcp, formatter, lsp do). If/when that's added, this skill is disable-able for free along with every other built-in.

Open questions for review

  • Is the description tight enough? Would you expect this to fire on a question like "how do I configure ESLint in my project" (it shouldn't)?
  • Is the dev/beta/local default-on the right rollout shape, or do you want stable-channel users to also see it from day one?
  • Anything in the body that's wrong / out of date / misleading? Particularly the permission-keys list and the plugin hook surface.

Test plan

  • bun run typecheck from packages/opencode clean
  • Smoke-imports the .md and confirms body is non-empty
  • Manual: start opencode on dev channel, ask the model to add a skill to opencode.json, confirm it produces {paths: [...]} not the array-of-objects shape
  • Manual: ask the model an unrelated question ("add a React component"), confirm the skill does NOT activate
  • Manual: set OPENCODE_EXPERIMENTAL_CUSTOMIZE_SKILL=false, restart, confirm the skill does not appear

Loads automatically and gives the model the actual shape of opencode.json,
agent/skill/plugin/MCP/permission definitions when it's asked to edit
opencode's own config. Avoids the recurring failure mode where the model
hallucinates a config shape and opencode hard-fails on startup.

Override via shadowing on disk (drop a same-named SKILL.md). No env flag
because skills as a category have no config-level disable today, so adding
one only here would be inconsistent.
kitlangton added 2 commits May 9, 2026 21:45
…ated fields

- Rename skill from opencode-meta to customize-opencode (better matches user
  intent, 'customize' is the verb users actually type).
- Rewrite the body in positive form: drop the wrong/right 'common shape
  mistakes' table, fold the disambiguation into a short list of positive
  shape statements per top-level field.
- Drop deprecated surfaces from the documented shapes: the deprecated 'mode'
  alias for 'agent', and the deprecated per-agent 'tools' field.
- Tighten prose throughout.
Default-on for dev/beta/local channels (mirrors the rollout pattern used by
OPENCODE_EXPERIMENTAL_HTTPAPI before it graduated). Stable users opt in via
the env var. Set =false to force off, =true to force on.

Factor the channel-default check into an unstableDefault() helper so future
experiments with the same rollout shape can reuse it.
@kitlangton kitlangton marked this pull request as ready for review May 10, 2026 01:54
@kitlangton kitlangton changed the title feat(skill): built-in opencode-meta skill (DRAFT) feat(skill): built-in opencode-meta skill May 10, 2026
The skill defaults ON for the 'local' channel (where CI runs), which made
disk-discovery tests off-by-one ('returns empty array when no skills
exist' returned 1, 'discovers N skills' returned N+1). Force the flag
off in preload so existing skill counts hold.
@kitlangton kitlangton merged commit 10ea590 into anomalyco:dev May 10, 2026
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant