Skip to content

System prompt relocation moves ALL plugin system.transform content to user messages — is this necessary? #210

@NamedIdentity

Description

@NamedIdentity

Problem

PR #148 introduced a workaround for Anthropic's OAuth validation by relocating system entries to the first user message. The current implementation in transforms.ts (transformBody) relocates all non-identity, non-billing system entries:

if (txt.startsWith(BILLING_PREFIX) || txt.startsWith(SYSTEM_IDENTITY)) {
    keptSystem.push(entry);  // kept in system[]
} else if (txt.length > 0) {
    movedTexts.push(txt);    // moved to first user message
}

This means every plugin using OpenCode's experimental.chat.system.transform hook to inject system-level instructions gets its content relocated from system[] to a user message. The system.transform hook exists specifically so plugins can provide system-level guidance to the model — the blanket relocation undermines that for every other plugin on the stack, not just OpenCode's own system content.

Concern

System prompt content and user message content serve different roles in the Anthropic API — the system parameter exists as a distinct concept precisely because it has different semantics. When plugin-injected system instructions are moved to a user message:

  • In short sessions, the relocated content is near the top of the conversation and still influential — no noticeable issue.
  • In longer sessions (150K+ tokens, 200+ messages), the first user message is far back in the conversation history. Instructions that were designed to be system-level persistent guidance are now competing with hundreds of subsequent messages for the model's attention.

We use multiple plugins that rely on system.transform for operational instructions (state management, agent coordination). In longer sessions, we've observed the model progressively ignoring these instructions — a pattern consistent with user-message content being deprioritized as conversation length grows, though we can't confirm the relocation is the direct cause since the issue is intermittent.

Question

Is the blanket relocation necessary, or would a more targeted approach work? For example:

  1. Only relocate OpenCode's own system content (e.g., the environment block that OpenCode builds), keeping plugin system.transform contributions in system[]
  2. Relocate based on content characteristics rather than "everything that isn't billing/identity"
  3. Provide an opt-out mechanism — a way for plugins to mark their system entries as needing system-level positioning

What exactly does Anthropic's validation check? Is it a strict allowlist that only permits the identity + billing entries in system[], or does it reject specific patterns? If additional system entries are permitted as long as they don't match certain rejection criteria, the relocation scope could be narrowed significantly.

Environment

  • OpenCode custom build (from source)
  • Multiple plugins using system.transform for operational instructions
  • Long sessions (150K+ tokens) where system instruction persistence matters
  • opencode-claude-auth@latest (currently 1.5.0)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions