Skip to content

Tool calls leak as plain text (stray "course"/"call" prefix) instead of executing when prose precedes the call #1499

Description

@AllanSantos-DV

Short summary

Tool calls leak as plain text (stray prefix "course"/"call") instead of executing when narrative text precedes the call

Affected version or release

GitHub Copilot app v1.0.10 (shared engine @github/copilot 1.0.65) · Windows · model: Claude Opus

What happened?

When an assistant turn begins with prose before a tool-call block, the call is rendered as plain text and never executes — sub-agents, shell, SQL and file edits silently don't run. The leaked block is consistently prefixed by a stray word that is the tail of the preceding prose word, e.g. course (from "Of course"), call (from "the call"), sure. When the tool call is the first content of the turn, it executes reliably.

This is a streaming/parse desync, not model output: the model emits a valid <function_calls> block; the client just fails to parse it once prose precedes it.

Root cause

The shipped engine is closed-source, so this is from the minified bundle @github/copilot/app.js. The text-based tool-call fallback is start-anchored:

let n = /^<functions><([^>]+)>([^>]+)</.exec(e.content); if (n) return [{toolName:n[1],input:n[2],id:""}]

^ requires the tag at offset 0. All 5 <functions> parse sites are anchored — there is no anywhere-scan. Text deltas are flushed per chunk before any tool scan:

this.streamingMessageText += n, this.session.emitEphemeral("assistant.message_delta", {messageId:this.currentMessageId, deltaContent:n})

So prose+markup in one content string is never re-scanned → leaks. The stray prefix is just the chunk-boundary tail of the last prose word.

Steps to reproduce

  1. Have the model start a turn with prose then a tool call (e.g. Of course\n<function_calls>...).
  2. Observe the markup printed as text; tool does not run.

Deterministic, parser-isolated repro (exact regex from bundle):

const p = c => /^<functions><([^>]+)>([^>]+)</.exec(c) ? 'EXECUTES' : 'LEAKS';
['Of course','make the call','sure'].forEach(s => console.log(p(s+'\n<functions><t>x</t></functions>'))); // 3x LEAKS
console.log(p('<functions><t>x</t></functions>')); // EXECUTES

Expected behavior

Parse the tool-call sentinel at any offset, buffering across stream chunks, so narrative-before-toolcall executes the same as toolcall-first. Fix: drop the ^ anchor / scan-anywhere.

Additional context

Related: github/copilot-cli#3765 (same symptom on CLI; shared engine).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions