🐛 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
You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
🐛 Bug: BuiltInAgent passes its system prompt inside messages, so AI SDK v6 prints a prompt-injection warning on every run #7846
import{BuiltInAgent}from"@copilotkit/runtime/v2";import{MockLanguageModelV3,simulateReadableStream}from"ai/test";constmodel=newMockLanguageModelV3({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},},},],}),}),});constagent=newBuiltInAgent({ model,prompt: "You are a helpful assistant."});awaitnewPromise((resolve,reject)=>agent.run({threadId: "t",runId: "r",messages: [{id: "m1",role: "user",content: "Hello"}],tools: [],context: [],state: {},forwardedProps: {},}).subscribe({complete: resolve,error: reject}),);constcall=model.doStreamCalls[0];console.log("prompt roles sent to the model:",call.prompt.map((m)=>m.role));
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' ]
⚠️ 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
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.
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.
Activity
copilotkit-outpost-bot commentedon Oct 16, 2026
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 themessagesarray viaunshift({ role: "system", ... })before being passed tostreamText({ 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
systemparameter tostreamText— 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 onmessages[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 👎
Laptopmini commentedon Oct 19, 2026
@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.
fix(runtime): pass BuiltInAgent's system prompt via streamText `syste…
fix(runtime): keep empty-conversation BuiltInAgent runs valid
Laptopmini commentedon Oct 19, 2026
Follow-up: I went through #7857 and its review, and updated #7881 based on it.
Your review on #7857 (an empty conversation leaves
messagesempty, and the AI SDK throws "messages must not be empty") applied to #7881 too. I confirmed it with the realstreamText: on 1.79.1 a run with no messages sends[system]and works, but with my first commit it threwInvalidPromptError. My learned-skills test missed it for the same reason, sincestreamTextwas mocked.The new commit on #7881:
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.MockLanguageModelV3tests 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 onmainand with the fix.Two other differences from #7857, in case they help you choose:
system, notmessages#7881 passessystemas an array ([catalog, prompt]), so the provider still gets two separate system messages in the same order as today. fix(runtime): keep agent instructions out of messages #7857 joins them into one string.systemhas takenSystemModelMessage[]sinceai6.0.104, the lowest version in the runtime's range.doStreamprompt from a packed build of fix(runtime): pass BuiltInAgent's system prompt via streamTextsystem, notmessages#7881 is byte-identical to 1.79.1's.Results: runtime 2965/2965,
tscpasses, lint/format unchanged. Run againstai6.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.