Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/cli/assets/claude/settings-hook.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"matcher": "Bash|Edit|Write|AskUserQuestion",
"matcher": "Bash|Edit|Write|MultiEdit|NotebookEdit|ExitPlanMode|AskUserQuestion",
"hooks": [
{
"type": "command",
Expand Down
31 changes: 31 additions & 0 deletions packages/cli/src/__tests__/services/setup/setup.service.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'fs';
import { tmpdir } from 'os';
import { join } from 'path';
import { fileURLToPath } from 'url';
import { createSetupService } from '../../../services/setup/setup.service.js';

describe('setup service', () => {
Expand Down Expand Up @@ -307,6 +308,36 @@ describe('setup service — claude agent', () => {
expect(existsSync(join(homeDir, '.claude', 'hooks', 'claude-prompt-hook.js'))).toBe(true);
});

it('installs the real settings-hook.json asset with a matcher covering every approval-required tool (regression: #110)', async () => {
fsMkdir(join(homeDir, '.claude'));

// Use the actual shipped asset (not the synthetic fixture below) so this
// test fails whenever the real matcher drifts, instead of only checking
// a copy that can silently go stale.
const realAssetRoot = fileURLToPath(new URL('../../../../assets', import.meta.url));
const service = createSetupService({
homeDir,
assetRoot: realAssetRoot,
runCommand: async () => {},
installBuiltInSkills: async () => {},
});

await service.run({ agents: ['claude'] });

const settings = JSON.parse(readFileSync(join(homeDir, '.claude', 'settings.json'), 'utf-8'));
const matcher: string = settings.hooks.PreToolUse[0].matcher;
const matchedTools = matcher.split('|');

// Every tool whose invocation surfaces a permission-approval or
// selection-question prompt must be captured here, or that prompt is
// silently dropped and never reaches Telegram (issue #110). ExitPlanMode
// is the plan-mode approval prompt; MultiEdit/NotebookEdit are batch
// file-edit approvals.
for (const tool of ['Bash', 'Edit', 'Write', 'MultiEdit', 'NotebookEdit', 'ExitPlanMode', 'AskUserQuestion']) {
expect(matchedTools).toContain(tool);
}
});

function createService() {
return createSetupService({
homeDir,
Expand Down