🐛 Bug: BuiltInAgent passes its system prompt inside `messages`, so AI SDK v6 prints a prompt-injection warning on every run · Issue #7846 · CopilotKit/CopilotKit · GitHub
Skip to content

🐛 Bug: BuiltInAgent passes its system prompt inside messages, so AI SDK v6 prints a prompt-injection warning on every run #7846

Description

@Laptopmini

♻️ Reproduction Steps

  1. npm i @copilotkit/runtime@1.79.1 ai@6 rxjs
  2. Save as repro.mjs:
import { BuiltInAgent } from "@copilotkit/runtime/v2";
import { MockLanguageModelV3, simulateReadableStream } from "ai/test";

const model = new MockLanguageModelV3({
  doStream: async () => ({
    stream: simulateReadableStream({
      chunks: [
        { type: "text-start", id: "t1" },
        { type: "text-delta", id: "t1", delta: "Hi!" },
        { type: "text-end", id: "t1" },
        {
          type: "finish",
          finishReason: { unified: "stop", raw: "stop" },
          usage: {
            inputTokens: { total: 1, noCache: 1, cacheRead: 0, cacheWrite: 0 },
            outputTokens: { total: 1, text: 1, reasoning: 0 },
          },
        },
      ],
    }),
  }),
});

const agent = new BuiltInAgent({ model, prompt: "You are a helpful assistant." });

await new Promise((resolve, reject) =>
  agent
    .run({
      threadId: "t",
      runId: "r",
      messages: [{ id: "m1", role: "user", content: "Hello" }],
      tools: [],
      context: [],
      state: {},
      forwardedProps: {},
    })
    .subscribe({ complete: resolve, error: reject }),
);

const call = model.doStreamCalls[0];
console.log("prompt roles sent to the model:", call.prompt.map((m) => m.role));
  1. node repro.mjs

The same warning shows up in a real app (BuiltInAgent with a prompt behind createCopilotRuntimeHandler, OpenAI-compatible provider) once per request.

✅ Expected Behavior

BuiltInAgent passes the prompt it builds itself (config prompt + app context + state, and the learned-skills catalog) through streamText({ system }), and messages holds only the conversation. The AI SDK then warns only when the app opts into forwardSystemMessages / forwardDeveloperMessages, which is the case the warning is meant for.

I checked that this sends the provider the same prompt: streamText({ system: "S", messages: [user] }) and streamText({ messages: [{ role: "system", content: "S" }, user] }) give identical doStream prompts with ai 6.0.308. The system version prints no warning.

❌ Actual Behavior

On every run, ai prints:

AI SDK Warning: System messages in the prompt or messages fields can be a security risk because they may enable prompt injection attacks. Use the system option instead when possible. Set allowSystemInMessages to true to suppress this warning, or false to throw an error.

Two runs print it twice. Since the agent's own prompt and client-forwarded system messages end up in the same array, an app can't tell the two apart from the warning. It also can't set allowSystemInMessages: false to reject client system messages, because that would reject the agent's own prompt too.

Cause, in packages/runtime/src/agent/index.ts on main (6e4cb6a):

  • ~L1386: messages.unshift({ role: "system", content: systemPrompt }) after convertMessagesToVercelAISDKMessages(...)
  • ~L1608: the same for learnedSkills.catalog

Then streamText({ messages, ... }). I haven't checked whether the factory (TanStack/custom) paths do the same.

Happy to open a PR: build one system string from both sources, pass it as system, and update the tests that expect messages[0].role === "system".

Screenshots

No response

𝌚 CopilotKit Version

├── @copilotkit/runtime@1.79.1


(`ai@6.0.308`, Node 24.14.1, macOS. Also seen with `@copilotkit/runtime` 1.79.0 + `ai` 6.0.306 on Cloudflare `wrangler dev` 4.151.0.)

📄 Logs (Optional)

$ node repro.mjs
AI SDK Warning: System messages in the prompt or messages fields can be a security risk because they may enable prompt injection attacks. Use the system option instead when possible. Set allowSystemInMessages to true to suppress this warning, or false to throw an error.
prompt roles sent to the model: [ 'system', 'user' ]

Activity

  1. copilotkit-outpost-bot commented on Oct 16, 2026

    @copilotkit-outpost-bot

    ⚠️ This is an AI-generated response. A member of our team will review and follow up if needed.

    The report is accurate. In packages/runtime/src/agent/index.ts, the agent's own prompt and the learned-skills catalog are both prepended into the messages array via unshift({ role: "system", ... }) before being passed to streamText({ messages, ... }) — which is exactly what triggers the AI SDK warning. (packages/runtime/src/agent/index.ts, lines ~1386 and ~1608.)

    Your proposed fix — consolidating both system sources into a single string and passing it as the system parameter to streamText — is the right direction. Engineering will need to verify whether the TanStack and custom factory paths have the same pattern, and whether any tests assert on messages[0].role === "system" in a way that would need updating. A PR would be welcome; the team will confirm scope when they pick this up.


    🤖 Generated by CopilotKit AI Support · Documentation


    Was this helpful? React with 👍 or 👎

  2. Laptopmini commented on Oct 19, 2026

    @Laptopmini
    ContributorAuthor

    @lukasmoschitz I didn't realize this had been address in another PR and just submitted a fix I had computed.

    Let me know if you prefer I close my PR in favor of the other one.

  3. added 2 commits that reference this issue on Oct 19, 2026
    16742c6
    232211f
  4. Laptopmini commented on Oct 19, 2026

    @Laptopmini
    ContributorAuthor

    Follow-up: I went through #7857 and its review, and updated #7881 based on it.

    Your review on #7857 (an empty conversation leaves messages empty, and the AI SDK throws "messages must not be empty") applied to #7881 too. I confirmed it with the real streamText: on 1.79.1 a run with no messages sends [system] and works, but with my first commit it threw InvalidPromptError. My learned-skills test missed it for the same reason, since streamText was mocked.

    The new commit on #7881:

    • Empty conversation: when there are no conversation messages yet (prompt-only or skills-catalog-only runs), the agent's system messages are sent as the conversation, the same shape as 1.79.1. Otherwise they go through system. In that one case the AI SDK still prints its warning, as it does today. I didn't use the empty user message fallback CodeRabbit suggested, because it changes what the provider receives and some providers (e.g. Anthropic) reject empty text content.
    • Tests: added the MockLanguageModelV3 tests you asked for: an empty conversation with a prompt, an empty conversation with only the skills catalog, and a prompt plus a user message. Both empty-conversation tests fail on my first commit with "messages must not be empty", and pass on main and with the fix.

    Two other differences from #7857, in case they help you choose:

    Results: runtime 2965/2965, tsc passes, lint/format unchanged. Run against ai 6.0.308 (the repo locks 6.0.156, which doesn't print the warning). The repro from this issue no longer warns, and an empty-conversation run returns [system] with no error.

    #7857 was opened first, so my earlier offer stands: if you'd rather continue there, I'll close #7881, and the tests and the empty-conversation handling are there to reuse.

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions