polish: claude-review iteration 7 feedback

- claude-code adapter: add 128-char safety cap on agent_id/agent_type
  so a malformed Claude Code payload cannot balloon DB rows. Empty
  strings now also treated as absent.
- migration010: state-aware debug log lists only columns actually
  added; idempotent re-runs log "already present; ensured indexes".
- Add 3 adapter tests covering the length cap boundary and empty-string
  rejection.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Alex Newman
2026-04-19 14:48:24 -07:00
parent 7872378535
commit b88d1c0c69
4 changed files with 154 additions and 98 deletions

File diff suppressed because one or more lines are too long

View File

@@ -2,6 +2,13 @@ import type { PlatformAdapter, NormalizedHookInput, HookResult } from '../types.
// Maps Claude Code stdin format (session_id, cwd, tool_name, etc.)
// SessionStart hooks receive no stdin, so we must handle undefined input gracefully
// Defensive cap: Claude Code's agent identifiers are short (e.g., "agent-abc123", "Explore").
// Ignore anything longer than 128 chars so a malformed payload cannot balloon DB rows.
const MAX_AGENT_FIELD_LEN = 128;
const pickAgentField = (v: unknown): string | undefined =>
typeof v === 'string' && v.length > 0 && v.length <= MAX_AGENT_FIELD_LEN ? v : undefined;
export const claudeCodeAdapter: PlatformAdapter = {
normalizeInput(raw) {
const r = (raw ?? {}) as any;
@@ -13,8 +20,8 @@ export const claudeCodeAdapter: PlatformAdapter = {
toolInput: r.tool_input,
toolResponse: r.tool_response,
transcriptPath: r.transcript_path,
agentId: typeof r.agent_id === 'string' ? r.agent_id : undefined,
agentType: typeof r.agent_type === 'string' ? r.agent_type : undefined,
agentId: pickAgentField(r.agent_id),
agentType: pickAgentField(r.agent_type),
};
},
formatOutput(result) {

View File

@@ -584,14 +584,18 @@ export const migration009: Migration = {
export const migration010: Migration = {
version: 27,
up: (db: Database) => {
const added: string[] = [];
const obsColumns = db.prepare('PRAGMA table_info(observations)').all() as Array<{ name: string }>;
const obsHasAgentType = obsColumns.some(c => c.name === 'agent_type');
const obsHasAgentId = obsColumns.some(c => c.name === 'agent_id');
if (!obsHasAgentType) {
db.run('ALTER TABLE observations ADD COLUMN agent_type TEXT');
added.push('observations.agent_type');
}
if (!obsHasAgentId) {
db.run('ALTER TABLE observations ADD COLUMN agent_id TEXT');
added.push('observations.agent_id');
}
db.run('CREATE INDEX IF NOT EXISTS idx_observations_agent_type ON observations(agent_type)');
db.run('CREATE INDEX IF NOT EXISTS idx_observations_agent_id ON observations(agent_id)');
@@ -604,13 +608,20 @@ export const migration010: Migration = {
const pendingHasAgentId = pendingColumns.some(c => c.name === 'agent_id');
if (!pendingHasAgentType) {
db.run('ALTER TABLE pending_messages ADD COLUMN agent_type TEXT');
added.push('pending_messages.agent_type');
}
if (!pendingHasAgentId) {
db.run('ALTER TABLE pending_messages ADD COLUMN agent_id TEXT');
added.push('pending_messages.agent_id');
}
}
logger.debug('DB', '[migration010] Added agent_type, agent_id columns to observations and pending_messages');
logger.debug(
'DB',
added.length > 0
? `[migration010] Added columns: ${added.join(', ')}`
: '[migration010] Subagent identity columns already present; ensured indexes'
);
},
down: (_db: Database) => {
// SQLite DROP COLUMN not fully supported; no-op

View File

@@ -79,4 +79,42 @@ describe('claudeCodeAdapter.normalizeInput — subagent fields', () => {
expect(normalizedUndef.agentId).toBeUndefined();
expect(normalizedUndef.agentType).toBeUndefined();
});
it('drops agent fields that exceed the 128-char safety cap', () => {
const oversized = 'a'.repeat(129);
const normalized = claudeCodeAdapter.normalizeInput({
session_id: 's1',
cwd: '/tmp',
agent_id: oversized,
agent_type: oversized,
});
expect(normalized.agentId).toBeUndefined();
expect(normalized.agentType).toBeUndefined();
});
it('keeps agent fields exactly at the 128-char boundary', () => {
const atLimit = 'a'.repeat(128);
const normalized = claudeCodeAdapter.normalizeInput({
session_id: 's1',
cwd: '/tmp',
agent_id: atLimit,
agent_type: atLimit,
});
expect(normalized.agentId).toBe(atLimit);
expect(normalized.agentType).toBe(atLimit);
});
it('drops empty-string agent fields (treat as absent)', () => {
const normalized = claudeCodeAdapter.normalizeInput({
session_id: 's1',
cwd: '/tmp',
agent_id: '',
agent_type: '',
});
expect(normalized.agentId).toBeUndefined();
expect(normalized.agentType).toBeUndefined();
});
});