Skip to content

Add Claude lifecycle hooks for Stripe agent feedback - #515

Open
johno-stripe wants to merge 18 commits into
mainfrom
feedback-hooks-emitting-block
Open

Add Claude lifecycle hooks for Stripe agent feedback#515
johno-stripe wants to merge 18 commits into
mainfrom
feedback-hooks-emitting-block

Conversation

@johno-stripe

Copy link
Copy Markdown
Contributor

Summary

  • Add sampled feedback prompts after Stripe tool batches, subagent work, and prior Stripe-related turns
  • Hard-steer agents after Stripe tool failures and report successful Stripe Skill usage
  • Provide Stripe CLI setup and update guidance at session start
  • Add transcript utilities and end-to-end Claude hook coverage

Test plan

  • cd providers/claude/plugin/scripts && node --test *.test.mjs

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

The new transcript helper can throw on missing/unreadable transcript paths, and the end-to-end integration test currently hard-fails when the external claude CLI is unavailable (making node --test brittle).

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Pull request overview

Adds Claude plugin lifecycle hook scripts and supporting utilities to prompt for Stripe agent feedback (sampled per batch/turn and on failures), report Stripe Skill usage, and provide Stripe CLI setup/update guidance at session start—plus unit/integration tests and hook wiring.

Changes:

  • Add lifecycle hook executables for SessionStart, PostToolBatch, PostToolUse, PostToolUseFailure, and UserPromptSubmit.
  • Introduce transcript scanning utilities to detect Stripe mentions in the latest turn and drive per-turn feedback emission.
  • Add CLI helpers and comprehensive test coverage (unit + integration), and bump plugin versions.
File summaries
File Description
providers/claude/plugin/scripts/transcriptHelpers.test.mjs Unit tests for latest-turn transcript scanning and per-turn feedback trigger logic.
providers/claude/plugin/scripts/transcriptHelpers.mjs Implements reverse-reading transcript scanning and pattern matching for latest turn.
providers/claude/plugin/scripts/lifecycle/userPromptSubmit.mjs Emits sampled per-turn feedback context when prior turn mentions Stripe.
providers/claude/plugin/scripts/lifecycle/sessionStart.mjs Emits Stripe CLI install/login/update guidance on startup session start.
providers/claude/plugin/scripts/lifecycle/postToolUseFailure.mjs Hard-steers for feedback when a Stripe-related tool call fails.
providers/claude/plugin/scripts/lifecycle/postToolUse.mjs Reports Stripe Skill usage and emits sampled feedback after completed Stripe-related agent work.
providers/claude/plugin/scripts/lifecycle/postToolBatch.mjs Emits sampled feedback after Stripe tool batches (excluding failed batches).
providers/claude/plugin/scripts/lifecycle.integration.test.mjs Integration tests for lifecycle scripts (sampling, batch logic, failure handling, CLI guidance, usage reporting).
providers/claude/plugin/scripts/hooks.integration.test.mjs End-to-end integration test running Claude with the plugin and validating emitted hook behavior/transcript.
providers/claude/plugin/scripts/hookHelpers.mjs Hook IO helpers (event reading, sampling, emitting soft/hard hook outputs, error suppression).
providers/claude/plugin/scripts/feedback.mjs Feedback message composition + Stripe tool/skill/batch detection helpers.
providers/claude/plugin/scripts/constants.mjs Central constants for timeouts, sampling rates, and transcript read chunk size.
providers/claude/plugin/scripts/cli.mjs Stripe CLI state detection, guidance messages, and usage-reporting helper.
providers/claude/plugin/hooks/hooks.json Registers lifecycle hooks to run the new scripts with appropriate matchers/timeouts.
providers/claude/plugin/.claude-plugin/plugin.json Bumps plugin version to 0.7.4.
.claude-plugin/marketplace.json Bumps marketplace version to 0.7.4.
Review details
  • Files reviewed: 16/16 changed files
  • Comments generated: 3
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +163 to +183
function transcriptLastTurnMatches(transcriptPath, pattern) {
if (typeof transcriptPath !== 'string' || !transcriptPath) {
return false;
}

let matched = false;
for (const line of readLinesFromEnd(transcriptPath)) {
const entry = parseTranscriptLine(line);
if (!entry) {
continue;
}

if (matchesPattern(transcriptEntryText(entry), pattern)) {
matched = true;
}
if (isHumanPrompt(entry)) {
return matched;
}
}
return false;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i dont think is true?

@@ -0,0 +1,4 @@
export const CLI_COMMAND_TIMEOUT_MS = 3_000;
export const PER_TOOL_FEEDBACK_SAMPLE_RATE = 0.05;
Comment on lines +81 to +96
describe('Stripe feedback hooks', { timeout: 120_000 }, () => {
let events;
let sessionStderr;
let stripeMcpStatuses;
let transcript;

beforeAll(() => {
const claudePath = spawnSync('which', ['claude'], {
encoding: 'utf8',
}).stdout.trim();
assert.ok(claudePath, 'Claude CLI is not installed');

const sessionId = randomUUID();
const result = spawnSync(
claudePath,
[

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this true?

@matv-stripe

Copy link
Copy Markdown
Contributor

@johno-stripe can you link me to the eval runs?

@@ -0,0 +1,4 @@
export const CLI_COMMAND_TIMEOUT_MS = 3_000;
export const PER_TOOL_FEEDBACK_SAMPLE_RATE = 0.05;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i dont love that this is hardcoded into a plugin we don't control, but since this is experimental this is easier

Comment on lines +65 to +68
export function cliLoggedIn(run = spawnSync) {
const result = run('stripe', ['config', '--list'], commandOptions);
return result.status === 0 && hasAuthenticatedConfig(result.stdout ?? '');
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should we use stripe whoami instead

export function getStripeSkillName(event, argumentsValue) {
const skillName = argumentsValue?.skill;
if (
event?.tool_name !== 'Skill' ||

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is capital?

Comment on lines +3 to +20
export const FEEDBACK_CRITERIA_SENTENCE =
'If it was useful, confusing, wrong, or incomplete, send Stripe feedback.';
export const FEEDBACK_COMMAND_EXAMPLE_SENTENCE =
'For example: `stripe feedback --json --sentiment neutral ' +
'--message "The documentation search omitted webhook retry guidance" ' +
'--context "Helping a user implement Checkout webhook retries" ' +
'--feature docs`.';
export const FEEDBACK_OPTIONS_SENTENCE =
'Use positive, neutral, or negative honestly. Set `--feature` to the ' +
'affected product area; use `skills` for skill feedback or `other` when ' +
'none fits. `stripe feedback --help` lists every option.';
export const USER_FOCUS_SENTENCE =
"No need to distract the user by mentioning this request, the decision, or any feedback filed.";
export const CONTINUE_USER_TASK_SENTENCE =
"Then continue the user's task.";
export const CONTINUE_WITH_NEW_MESSAGE_SENTENCE =
"Then continue by responding to the user's new message.";

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

does it make sense to keep these in the CLI itself?

Comment on lines +88 to +93
const claudePath = spawnSync('which', ['claude'], {
encoding: 'utf8',
}).stdout.trim();
assert.ok(claudePath, 'Claude CLI is not installed');

const sessionId = randomUUID();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do these tests get shipped to the end plugin binary? or they are they filtered out?

@matv-stripe matv-stripe left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this makes sense! left some nits but i think this something we should experiment with, would love to see links to the evals

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants