mirror of
https://github.com/garrytan/gstack.git
synced 2026-09-11 15:39:04 +02:00
fix(question-log): parse native AskUserQuestion answers — every native answer logged as __unknown__
Current Claude Code returns AskUserQuestion results as an OBJECT map keyed
by question text ({answers: {question: label}}); the hook only handled the
legacy array shapes, so 86% of live records carried user_choice __unknown__
— and the bin then scored every one as followed_recommendation false,
silently poisoning plan-tune metrics. Adds the object-map extraction (exact
+ whitespace-normalized + single-question pairing, multiSelect joins,
annotations as free_text), strips the (Recommended) suffix from BOTH sides
of the comparison, skips the computation entirely on extraction failure,
and logs unrecognized shapes to hook-errors.log instead of embedding them
in the record.
Fixes #2336, #2206.
Based on the working patch in #2336 by @yijisoo; suffix comparison fix
contributed by @chuchu2781 (PR #2400).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
540847d038
commit
54612cb9e3
@@ -138,6 +138,91 @@ describe('PostToolUse hook (native AskUserQuestion)', () => {
|
||||
});
|
||||
});
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// Shape D: current native AUQ result — answers object keyed by question text
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
describe('PostToolUse hook (Shape D: answers object keyed by question text)', () => {
|
||||
test('captures user_choice from the answers map, stripping the (Recommended) suffix', () => {
|
||||
const qText = 'D-shape test: apply the edits?';
|
||||
runHook({
|
||||
session_id: 'sessD1',
|
||||
tool_name: 'AskUserQuestion',
|
||||
tool_use_id: 'tu-d1',
|
||||
tool_input: {
|
||||
questions: [
|
||||
{ question: qText, options: [{ label: 'Full revision (Recommended)' }, { label: 'Hold' }] },
|
||||
],
|
||||
},
|
||||
tool_response: { answers: { [qText]: 'Full revision (Recommended)' } },
|
||||
cwd: ROOT,
|
||||
});
|
||||
const events = readLog();
|
||||
expect(events.length).toBe(1);
|
||||
expect(events[0].user_choice).toBe('Full revision');
|
||||
expect(events[0].recommended).toBe('Full revision');
|
||||
expect(events[0].followed_recommendation).toBe(true);
|
||||
});
|
||||
|
||||
test('free-text answer (not an option label) → source=auq-other with free_text', () => {
|
||||
const qText = 'D-shape free text test';
|
||||
runHook({
|
||||
session_id: 'sessD2',
|
||||
tool_name: 'AskUserQuestion',
|
||||
tool_use_id: 'tu-d2',
|
||||
tool_input: {
|
||||
questions: [{ question: qText, options: [{ label: 'Alpha' }, { label: 'Beta' }] }],
|
||||
},
|
||||
tool_response: { answers: { [qText]: 'I cannot see what the proposal is' } },
|
||||
cwd: ROOT,
|
||||
});
|
||||
const events = readLog();
|
||||
expect(events.length).toBe(1);
|
||||
expect(events[0].source).toBe('auq-other');
|
||||
expect(events[0].free_text).toContain('cannot see');
|
||||
});
|
||||
|
||||
test('annotations notes are captured as free_text alongside a selected option', () => {
|
||||
const qText = 'D-shape annotations test';
|
||||
runHook({
|
||||
session_id: 'sessD3',
|
||||
tool_name: 'AskUserQuestion',
|
||||
tool_use_id: 'tu-d3',
|
||||
tool_input: {
|
||||
questions: [{ question: qText, options: [{ label: 'Alpha' }, { label: 'Beta' }] }],
|
||||
},
|
||||
tool_response: {
|
||||
answers: { [qText]: 'Alpha' },
|
||||
annotations: { [qText]: { notes: 'but only after the demo' } },
|
||||
},
|
||||
cwd: ROOT,
|
||||
});
|
||||
const events = readLog();
|
||||
expect(events.length).toBe(1);
|
||||
expect(events[0].user_choice).toBe('Alpha');
|
||||
expect(events[0].free_text).toContain('after the demo');
|
||||
});
|
||||
|
||||
test('empty tool_response → user_choice __unknown__ and NO followed_recommendation', () => {
|
||||
runHook({
|
||||
session_id: 'sessD4',
|
||||
tool_name: 'AskUserQuestion',
|
||||
tool_use_id: 'tu-d4',
|
||||
tool_input: {
|
||||
questions: [
|
||||
{ question: 'D-shape unknown test', options: [{ label: 'Alpha (Recommended)' }, { label: 'Beta' }] },
|
||||
],
|
||||
},
|
||||
cwd: ROOT,
|
||||
});
|
||||
const events = readLog();
|
||||
expect(events.length).toBe(1);
|
||||
expect(events[0].user_choice).toBe('__unknown__');
|
||||
expect(events[0].recommended).toBe('Alpha');
|
||||
expect(events[0].followed_recommendation).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// MCP AskUserQuestion variant (Conductor)
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user