anthropics / anthropics/claude-agent-sdk-typescript
options.hooks causes agent to ignore custom systemPrompt and identify as "Claude Code"
- Lingua principale
- Shell
- Stelle
- 1.8k
- Fork
- 226
- Metriche di merge delle PR
- Nessuna PR unita negli ultimi 30g
Descrizione
### Summary
When passing `options.hooks` (any of `PreToolUse`, `PostToolUse`, `PreCompact`, or `SessionEnd`) to `query()`, the agent **ignores the custom `systemPrompt`** when asked about its identity and responds as "Claude Code / assistant" — overriding the persona defined in the systemPrompt.
The bug is selective: operational tasks work normally. It only manifests on identity-type queries ("who are you?", "what is your role?").
Without hooks, the same systemPrompt produces correct identity. **Replacing hooks with `canUseTool` callback works correctly.**
### Environment
- `@anthropic-ai/claude-agent-sdk`: `0.2.138+`
- Node.js: `v20.20.2`
- Model: `claude-sonnet-4-6`
- OS: Linux (Ubuntu 24.04)
- Auth: Claude Max subscription (no API key)
### Minimal reproduction
```javascript
import { query } from '@anthropic-ai/claude-agent-sdk';
const BASE_OPTIONS = {
model: 'sonnet',
systemPrompt: 'You are "Pepe", Account Manager at BDB Agency. Your only role is Account Manager.',
maxTurns: 2,
permissionMode: 'dontAsk',
settingSources: [],
cwd: process.cwd(),
};
const PROMPT = 'Who are you? One line.';
const noop = async () => {};
async function test(name, extra) {
console.log(`\n=== ${name} ===`);
const r = query({ prompt: PROMPT, options: { ...BASE_OPTIONS, ...extra } });
for await (const msg of r) {
if (msg.type === 'assistant' && msg.message?.content) {
for (const block of msg.message.content) {
if (block.type === 'text') console.log(' AGENT:', block.text);
}
}
}
}
// Test 1: BASE (no hooks)
await test('No hooks', {});
// Test 2: with PreToolUse hook
await test('With PreToolUse hook', {
hooks: { PreToolUse: [{ matcher: '.*', handler: noop }] },
});
// Test 3: with only SessionEnd hook (no tool hooks at all)
await test('With SessionEnd hook only', {
hooks: { SessionEnd: [{ handler: noop }] },
});
// Test 4: with canUseTool (alternative)
await test('With canUseTool (alternative)', {
canUseTool: async (toolName, input) => ({ behavior: 'allow', updatedInput: input }),
});
```
### Expected behavior
All 4 tests should respond with the persona from `systemPrompt` (e.g., "I am Pepe, Account Manager at BDB Agency.").
### Actual behavior
- Test 1 (no hooks): ✅ `"I am Pepe, Account Manager at BDB Agency."`
- Test 2 (PreToolUse): ❌ `"I am Claude, an AI assistant..."` or `"I am a software engineering assistant..."`
- Test 3 (SessionEnd only): ❌ Same identity bleed — proving it's not specific to tool hooks
- Test 4 (canUseTool): ✅ Correct identity
### Observations
1. **`settingSources: []` does NOT fix it.** Already set explicitly in the reproduction.
2. **Any hook type triggers it**, even `SessionEnd` or `PreCompact` (not just tool-related).
3. **Even empty handlers (`async () => {}`) trigger it.**
4. **Passing `systemPrompt` as `{ type: 'preset', preset: 'claude_code', append: '...' }` does NOT fix it** when hooks are active.
5. **`canUseTool` callback works correctly** and provides equivalent functionality for tool permission control.
### Impact
Production multi-agent systems that:
- Use custom personas via `systemPrompt`
- Need observability hooks (tool logging, budget guards, session lifecycle events)
...have to choose between identity fidelity and observability. Workaround exists (`canUseTool`) but doesn't cover `PostToolUse`, `PreCompact`, or `SessionEnd` use cases.
### Workaround we applied
Migrated from `hooks.PreToolUse` → `options.canUseTool`. For post-tool logging, we parse the `result.messages[]` array after the query completes instead of using `PostToolUse`. We removed `PreCompact` and `SessionEnd` (logs are non-critical, replaced with post-result logging).
### Discovery context
Discovered while building a 17-agent enterprise system on Claude Agent SDK. Spent ~8 hours iterating through 9 hypotheses before isolating the bug to `options.hooks` specifically. Tests in `scripts/test-isolate-hooks.mjs` (1-6) and `scripts/test-canusetool-alt.mjs` (E-G) reproduce the isolation deterministically.
### Questions for maintainers
1. Is `options.hooks` documented to affect identity, or is this an internal preset switch we're not aware of?
2. Is there a way to keep `PostToolUse` (or `PreCompact`/`SessionEnd`) without identity drift?
3. Would you accept a PR adding a doc note about this interaction, or do you prefer a code fix?
---
Happy to provide more context or extend the reproduction. Thanks for the SDK.
Guida per i contributori
Nessuna guida per i contributori indicizzata per questo repository
Valutazione
Questa issue non è ancora stata valutata.