Skip to content

Commit 5ff401d

Browse files
committed
feat: add keybinding-customization patch to bypass feature flag gate
Custom keybindings from ~/.claude/keybindings.json are blocked when CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=1 because the feature flag tengu_keybinding_customization_release defaults to false without GrowthBook. This patch force-enables the gate function.
1 parent 0cf8b15 commit 5ff401d

3 files changed

Lines changed: 81 additions & 0 deletions

File tree

src/patches/index.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ import { writeWorktreeMode } from './worktreeMode';
7373
import { writeAllowCustomAgentModels } from './allowCustomAgentModels';
7474
import { writeVoiceMode } from './voiceMode';
7575
import { writeChannelsMode } from './channelsMode';
76+
import { writeKeybindingCustomization } from './keybindingCustomization';
7677
import {
7778
restoreNativeBinaryFromBackup,
7879
restoreClijsFromBackup,
@@ -174,6 +175,13 @@ const PATCH_DEFINITIONS = [
174175
group: PatchGroup.ALWAYS_APPLIED,
175176
description: `Statusline updates will be properly throttled instead of queued (or debounced)`,
176177
},
178+
{
179+
id: 'keybinding-customization',
180+
name: 'Keybinding customization',
181+
group: PatchGroup.ALWAYS_APPLIED,
182+
description:
183+
'Force-enable custom keybindings when CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=1',
184+
},
177185
// Misc Configurable
178186
{
179187
id: 'model-customizations',
@@ -663,6 +671,9 @@ export const applyCustomization = async (
663671
),
664672
condition: config.settings.misc?.statuslineThrottleMs != null,
665673
},
674+
'keybinding-customization': {
675+
fn: c => writeKeybindingCustomization(c),
676+
},
666677
// Misc Configurable
667678
'patches-applied-indication': {
668679
fn: c =>
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import { describe, expect, it } from 'vitest';
2+
3+
import { writeKeybindingCustomization } from './keybindingCustomization';
4+
5+
describe('keybindingCustomization', () => {
6+
it('bypasses the keybinding customization gate', () => {
7+
const file =
8+
'const x=1;' +
9+
'function lE(){return u$("tengu_keybinding_customization_release",!1)}' +
10+
'function DLK(H){let $=new Date()}';
11+
12+
const result = writeKeybindingCustomization(file);
13+
14+
expect(result).not.toBeNull();
15+
expect(result).toContain(
16+
'function lE(){return !0;return u$("tengu_keybinding_customization_release",!1)}'
17+
);
18+
});
19+
20+
it('returns unchanged file when already patched', () => {
21+
const file =
22+
'const x=1;' +
23+
'function lE(){return !0;return u$("tengu_keybinding_customization_release",!1)}' +
24+
'function DLK(H){let $=new Date()}';
25+
26+
const result = writeKeybindingCustomization(file);
27+
28+
expect(result).toBe(file);
29+
});
30+
31+
it('returns null when the gate pattern is absent', () => {
32+
expect(writeKeybindingCustomization('const x=1;')).toBeNull();
33+
});
34+
});
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import { showDiff } from './index';
2+
import { debug } from '../utils';
3+
4+
export const writeKeybindingCustomization = (
5+
oldFile: string
6+
): string | null => {
7+
const alreadyPatched =
8+
/function [$\w]+\(\)\{return !0;return [$\w]+\("tengu_keybinding_customization_release"/;
9+
10+
if (alreadyPatched.test(oldFile)) {
11+
debug('patch: keybindingCustomization: already patched');
12+
return oldFile;
13+
}
14+
15+
const pattern =
16+
/function [$\w]+\(\)\{return [$\w]+\("tengu_keybinding_customization_release"/;
17+
18+
const match = oldFile.match(pattern);
19+
20+
if (!match || match.index === undefined) {
21+
debug(
22+
'patch: keybindingCustomization: failed to find keybinding customization gate pattern'
23+
);
24+
return null;
25+
}
26+
27+
const insertIndex = match.index + match[0].indexOf('{') + 1;
28+
const insertion = 'return !0;';
29+
30+
const newFile =
31+
oldFile.slice(0, insertIndex) + insertion + oldFile.slice(insertIndex);
32+
33+
showDiff(oldFile, newFile, insertion, insertIndex, insertIndex);
34+
35+
return newFile;
36+
};

0 commit comments

Comments
 (0)