Skip to content

agents: V1 theme color names silently drop agent files in V2 #54057

Closed

Description

@egofinder

Summary

V2 silently drops any agent whose frontmatter color is a V1 theme color name (primary, secondary, accent, success, warning, error, info). The agent disappears from the agent registry with no warning. In a real global config this dropped 8 of 9 custom agents. V1 accepted these names, and the migration guide says V2 translates legacy agent frontmatter automatically, so this is a compatibility regression rather than an expected migration step.

Environment

  • opencode version: 2.0.26
  • OS: Darwin 27.0.0 (macOS, arm64)
  • Terminal: ghostty (TERM_PROGRAM=ghostty, TERM=xterm-256color, COLORTERM=truecolor)
  • Shell: /bin/zsh
  • Install/channel: Homebrew (opencode-v2 formula, latest)
  • Active plugins: none

Reproduction

  1. Create a global agent file ~/.config/opencode/agents/demo.md using a V1 theme color name:

    ---
    description: demo agent
    mode: subagent
    color: accent
    ---
    
    Body.
  2. Reload/start OpenCode and inspect the agent registry:

    opencode debug agents
    # or
    opencode api get /api/agent
  3. demo is absent from the list.

  4. Change only the color to a six-digit hex:

    color: "#9d7cd8"
  5. demo now appears.

Expected Behavior

Per the V1→V2 migration guide, "V2 translates legacy agent frontmatter automatically", and the V1 docs list theme color names (primary, secondary, accent, success, warning, error, info) as valid color values. Existing agents using those names should keep loading (and ideally resolve to the theme's corresponding color). V2's current agent docs describe color as a six-digit hex value, so a graceful translation or at least a warning is expected.

Actual Behavior

An agent whose color is a theme color name is silently omitted from the agent registry. No warning or error is logged, and no other fields are reported. A hex color loads correctly.

The failure is also masked by the V1 permission field: an agent that has a V1 permission block still loads despite a theme-name color, apparently because it takes a legacy parse path. Its color then resolves to the fallback #aaaaaa instead of the theme color. Converting that block to the documented V2 permissions array (as the migration guide instructs) removes the legacy path, and the agent is then dropped — so applying the documented migration can break an agent that previously worked.

Concretely, with a file like:

---
description: orchestrator
mode: subagent
color: warning
permissions:
  - action: subagent
    resource: "*"
    effect: deny
---
Body.

the agent is not registered, while the same file with permission: { task: { "*": deny } } (V1) is registered.

Additional Context

  • Verified repeatedly via both opencode debug agents and opencode api get /api/agent; results were consistent once the async reload settled.
  • Controlled tests: color: accent, color: warning, and color: success each dropped the agent; color: "#ff6b6b" and omitting color both loaded.
  • This affects the preferred V2 global location ~/.config/opencode/agents/<name>.md; existing V1 setups commonly use theme color names.
  • Workaround: replace theme color names with a six-digit hex before migrating, or leave the agent's permission block in the V1 form.
  • Impact: agents disappear silently, so users may believe a migrated agent is active when it is not.

Activity

  1. opencode-agent commented on Oct 9, 2026

    @opencode-agent
    Contributor

    Thanks for the detailed report. I reproduced this on 2.0.26, and it still happens on the latest v2 commit (eefe85d) run from source.

    I used a separate, empty config directory with global agents in ~/.config/opencode/agents/, then ran opencode debug agents (repeating it until the list settled):

    • color: accent, color: warning and color: success each make the agent disappear, with no warning.
    • color: "#9d7cd8" and no color both load.
    • color: warning with a v2 permissions array: the agent is dropped.
    • color: warning with a v1 permission block: the agent loads, but its color becomes #aaaaaa.

    From reading the code, it looks like files with only v2 fields are checked against a hex-only color rule, and a file that fails that check is skipped without a log message. Files with v1-only fields go through the legacy translation, which accepts the color name but replaces it with #aaaaaa.

    The script below reproduces it. Until this is fixed, your workaround of using a six-digit hex color keeps the agents loading.

    Reproduction script (repro.sh)
    #!/usr/bin/env bash
    # Repro for #54057: V1 theme color names silently drop agent files in V2.
    # Usage: OC="opencode" bash repro.sh   (or OC="bun ... packages/cli/src/index.ts")
    set -u
    OC=${OC:-opencode}
    ROOT=$(mktemp -d /tmp/opencode/oc54057.XXXX)
    export HOME=$ROOT/home XDG_CONFIG_HOME=$ROOT/home/.config XDG_DATA_HOME=$ROOT/home/.local/share XDG_STATE_HOME=$ROOT/home/.local/state XDG_CACHE_HOME=$ROOT/home/.cache
    A=$XDG_CONFIG_HOME/opencode/agents; mkdir -p $A $ROOT/proj
    mk(){ printf -- "---\ndescription: $1 agent\nmode: subagent\n$2\n---\nBody.\n" > $A/$1.md; }
    mk theme_accent  'color: accent'
    mk theme_warning 'color: warning'
    mk theme_success 'color: success'
    mk hex_color     'color: "#9d7cd8"'
    mk no_color      ''
    mk warn_v2perm   $'color: warning\npermissions:\n  - action: subagent\n    resource: "*"\n    effect: deny'
    mk warn_v1perm   $'color: warning\npermission:\n  task:\n    "*": deny'
    cd ${RUN_DIR:-$ROOT/proj}
    $OC --version
    $OC service stop >/dev/null 2>&1 || true
    # agent loading is async after startup: poll until the list settles
    for i in 1 2 3 4 5 6; do OUT=$($OC debug agents 2>&1); grep -q no_color <<<"$OUT" && break; sleep 2; done
    echo "$OUT" > $ROOT/out.txt; echo "full output: $ROOT/out.txt"
    for n in theme_accent theme_warning theme_success hex_color no_color warn_v2perm warn_v1perm; do
      if grep -q "$n" <<<"$OUT"; then echo "$n: LOADED"; else echo "$n: MISSING"; fi
    done
    echo "--- warn_v1perm color:"; grep -A12 "\"id\": \"warn_v1perm\"" <<<"$OUT" | grep color
    
    $OC service stop >/dev/null 2>&1 || true

    Run it: bash repro.sh

  2. abinawahasan-bot commented on Oct 18, 2026

    @abinawahasan-bot

    Confirming on the current release: this still reproduces on 2.0.32.

    Real-world impact (a real global ~/.config/opencode/agents/ with 58 custom agent files): 13 of 58 were silently dropped — the ones whose frontmatter had only description / mode / color: <theme name> and no V1-only key. The dropped files included orchestrator (mode: all), architect, and the developer/engineer/reviewer agents. Because orchestrator was still selectable as the configured default_agent, it kept running with an empty system prompt (0 chars) — its ~18K prompt was silently lost, with no warning anywhere.

    Controlled matrix on 2.0.32

    Base frontmatter description + mode: subagent; only the last field(s) varied. Checked after a full server restart (opencode reload immediately after creating the files is racy — the list can read empty until the async scan settles):

    frontmatter additions loaded
    color: accent ❌ dropped
    color: "#ff8800" ✅
    (no color) ✅
    color: accent + permission: {} ✅
    color: accent + temperature: 0.3 ✅
    color: accent + top_p: 0.9 ✅
    color: accent + hidden: false ❌ dropped

    Refinement to the two-parse-path diagnosis

    The rescue is not "any extra key" — it is specifically a V1-only field (permission, temperature, top_p) forcing the legacy path. hidden is accepted by both schemas, so hidden: false does not rescue the file. That matches: files that don't hit the legacy path are validated against the hex-only color rule and dropped without a log line.

    Minimal, behavior-preserving workaround

    Instead of converting theme names to hex (loses the theme color) or keeping a V1 permission block, an empty V1 block is enough:

    color: accent
    permission: {}

    permission: {} yields an identical effective permission ruleset to having no permission key at all, so it changes no behavior while keeping the theme color name.

    Just adding the release confirmation, the real-world scope, and the hidden data point on top of the root-cause analysis above.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions