Skip to content

Instantly share code, notes, and snippets.

@odysseus0
Created March 2, 2026 22:52
Show Gist options
  • Select an option

  • Save odysseus0/96a5d63bb675129943b0b59cb81e2da6 to your computer and use it in GitHub Desktop.

Select an option

Save odysseus0/96a5d63bb675129943b0b59cb81e2da6 to your computer and use it in GitHub Desktop.
Repro: GMAIL_BATCH_MODIFY_MESSAGES fabricates modified_count (Composio bug)
/**
* Repro: GMAIL_BATCH_MODIFY_MESSAGES fabricates modified_count
*
* Gmail's batchModify returns 204 No Content — no count. Composio synthesizes
* modified_count by echoing back len(messageIds), even for nonexistent IDs.
*
* Requires: COMPOSIO_API_KEY in env, Gmail OAuth (opens browser)
* Run: bun run scripts/repro-batch-modify-bug.ts
*/
import { Composio } from '@composio/core'
const composio = new Composio({ apiKey: process.env.COMPOSIO_API_KEY! })
const userId = `repro-${Date.now()}`
async function mcpCall(mcp: { url: string; headers: Record<string, string> }, tool: string, args: Record<string, unknown>) {
const res = await fetch(mcp.url, {
method: 'POST',
headers: { ...mcp.headers, 'Content-Type': 'application/json', Accept: 'application/json, text/event-stream' },
body: JSON.stringify({ jsonrpc: '2.0', method: 'tools/call', params: { name: tool, arguments: args }, id: 1 }),
})
const text = await res.text()
for (const line of text.split('\n')) {
if (!line.startsWith('data: ')) continue
const parsed = JSON.parse(line.slice(6))
if (parsed?.result) return JSON.parse(parsed.result.content[0].text)
}
}
// 1. Connect Gmail
const session = await composio.create(userId)
const auth = await session.authorize('gmail')
console.log(`Complete OAuth: ${auth.redirectUrl}`)
await Bun.spawn(['open', auth.redirectUrl]).exited
const conn = await auth.waitForConnection(120_000)
console.log(`Connected: ${conn.id}\n`)
const { mcp } = await composio.create(userId, { toolkits: ['gmail'] })
// 2. Get 2 real message IDs
const fetchResult = await mcpCall(mcp, 'COMPOSIO_MULTI_EXECUTE_TOOL', {
tools: [{ tool_slug: 'GMAIL_FETCH_EMAILS', arguments: { query: 'in:inbox newer_than:7d', max_results: 2 } }],
})
const messages = fetchResult.data.results[0].response.data.messages
const realIds = messages.map((m: { messageId: string }) => m.messageId)
console.log(`Real IDs: ${realIds.join(', ')}`)
// 3. Batch modify with 2 real + 2 fake IDs
const fakeIds = ['1000000000000001', '1000000000000002']
const allIds = [...realIds, ...fakeIds]
console.log(`Sending batch modify with ${allIds.length} IDs (${realIds.length} real, ${fakeIds.length} fake)\n`)
const batchResult = await mcpCall(mcp, 'COMPOSIO_MULTI_EXECUTE_TOOL', {
tools: [{ tool_slug: 'GMAIL_BATCH_MODIFY_MESSAGES', arguments: { messageIds: allIds, addLabelIds: ['STARRED'] } }],
})
console.log('Result:', JSON.stringify(batchResult.data.results[0].response.data, null, 2))
console.log(`\nExpected modified_count: ${realIds.length} (only real messages exist)`)
console.log(`Actual modified_count: ${batchResult.data.results[0].response.data.modified_count}`)
// Cleanup
await mcpCall(mcp, 'COMPOSIO_MULTI_EXECUTE_TOOL', {
tools: [{ tool_slug: 'GMAIL_BATCH_MODIFY_MESSAGES', arguments: { messageIds: realIds, removeLabelIds: ['STARRED'] } }],
})
await composio.connectedAccounts.delete(conn.id)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment