Repository navigation
fix: preserve native prompt cache reads #1366
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
fae0d51
09cca33
a3b8929
69b7b80
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -494,6 +494,18 @@ export async function runHeadless( | |||||||||||
| setSDKStatus?: (status: SDKStatus) => void | ||||||||||||
| }, | ||||||||||||
| ): Promise<void> { | ||||||||||||
| // Native messages slot host: exclusive stdin/stdout kin_* protocol. Enter | ||||||||||||
| // before Grove / structuredIO so a live kernel pipe is never treated as a | ||||||||||||
| // -p prompt. CLI holds no tools/agents/canUseTool — the caller executes | ||||||||||||
| // every tool_use itself (see .trellis/tasks/08-30-native-slot-stateless). | ||||||||||||
| if (process.env.CLAUDE_CODE_KIN_NATIVE_SLOTS) { | ||||||||||||
| const { runNativeMessagesLoop } = await import( | ||||||||||||
| '../kin/nativeMessagesRunner.js' | ||||||||||||
| ) | ||||||||||||
| await runNativeMessagesLoop({ options }) | ||||||||||||
| return | ||||||||||||
|
Comment on lines
+505
to
+506
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win Shut down the process when the native loop ends.
🐛 Proposed fix await runNativeMessagesLoop({ options })
+ gracefulShutdownSync(0)
returnInside 📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| if ( | ||||||||||||
| process.env.USER_TYPE === 'ant' && | ||||||||||||
| isEnvTruthy(process.env.CLAUDE_CODE_EXIT_AFTER_FIRST_RENDER) | ||||||||||||
|
|
||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,153 @@ | ||
| import { mkdtempSync, rmSync, writeFileSync } from 'node:fs' | ||
| import { tmpdir } from 'node:os' | ||
| import { join } from 'node:path' | ||
| import { afterEach, describe, expect, test } from 'bun:test' | ||
| import { readKinRuntimeConfig } from '../runtimeConfig' | ||
| import { | ||
| COMPACT_IDENTITY, | ||
| IDENTITY, | ||
| getSystemLayout, | ||
| layoutSystemBlocks, | ||
| } from '../systemLayout' | ||
|
|
||
| const originalEnv = { | ||
| path: process.env.KIN_KERNEL_CONFIG_PATH, | ||
| native: process.env.CLAUDE_CODE_KIN_NATIVE_SLOTS, | ||
| layout: process.env.CLAUDE_CODE_SYSTEM_LAYOUT, | ||
| mode: process.env.KIN_SYSTEM_MODE, | ||
| } | ||
|
|
||
| function restoreEnv(): void { | ||
| for (const [key, value] of [ | ||
| ['KIN_KERNEL_CONFIG_PATH', originalEnv.path], | ||
| ['CLAUDE_CODE_KIN_NATIVE_SLOTS', originalEnv.native], | ||
| ['CLAUDE_CODE_SYSTEM_LAYOUT', originalEnv.layout], | ||
| ['KIN_SYSTEM_MODE', originalEnv.mode], | ||
| ] as const) { | ||
| if (value === undefined) delete process.env[key] | ||
| else process.env[key] = value | ||
| } | ||
| } | ||
|
|
||
| afterEach(restoreEnv) | ||
|
|
||
| describe('Kin runtime configuration', () => { | ||
| test('reads updated kernel config on every native job', () => { | ||
| const root = mkdtempSync(join(tmpdir(), 'kin-runtime-config-')) | ||
| const path = join(root, 'kernel.json') | ||
| process.env.KIN_KERNEL_CONFIG_PATH = path | ||
| process.env.CLAUDE_CODE_KIN_NATIVE_SLOTS = '1' | ||
| try { | ||
| writeFileSync( | ||
| path, | ||
| JSON.stringify({ | ||
| default_cache_ttl: '5m', | ||
| persona_preset: 'zero', | ||
| system_layout: 'zero', | ||
| timezone: 'UTC', | ||
| }), | ||
| ) | ||
| expect(readKinRuntimeConfig()).toMatchObject({ | ||
| default_cache_ttl: '5m', | ||
| persona_preset: 'zero', | ||
| system_layout: 'zero', | ||
| timezone: 'UTC', | ||
| }) | ||
| writeFileSync( | ||
| path, | ||
| JSON.stringify({ | ||
| default_cache_ttl: '1h', | ||
| persona_preset: 'official_full', | ||
| system_layout: 'identity', | ||
| timezone: 'America/Los_Angeles', | ||
| }), | ||
| ) | ||
| expect(readKinRuntimeConfig()).toMatchObject({ | ||
| default_cache_ttl: '1h', | ||
| persona_preset: 'official_full', | ||
| system_layout: 'identity', | ||
| timezone: 'America/Los_Angeles', | ||
| }) | ||
| } finally { | ||
| rmSync(root, { recursive: true, force: true }) | ||
| } | ||
| }) | ||
|
|
||
| test('native file config overrides fixed standalone layout environment', () => { | ||
| const root = mkdtempSync(join(tmpdir(), 'kin-runtime-layout-')) | ||
| const path = join(root, 'kernel.json') | ||
| process.env.KIN_KERNEL_CONFIG_PATH = path | ||
| process.env.CLAUDE_CODE_KIN_NATIVE_SLOTS = '1' | ||
| process.env.CLAUDE_CODE_SYSTEM_LAYOUT = 'zero' | ||
| process.env.KIN_SYSTEM_MODE = 'zero' | ||
| try { | ||
| writeFileSync( | ||
| path, | ||
| JSON.stringify({ | ||
| default_cache_ttl: '1h', | ||
| persona_preset: 'official', | ||
| system_layout: 'identity', | ||
| }), | ||
| ) | ||
| expect(getSystemLayout(readKinRuntimeConfig())).toBe('identity') | ||
| } finally { | ||
| rmSync(root, { recursive: true, force: true }) | ||
| } | ||
| }) | ||
|
|
||
| test('layoutSystemBlocks keeps the three persona contracts distinct', () => { | ||
| const common = { | ||
| attribution: 'x-anthropic-billing-header: cc_version=test;', | ||
| } | ||
| const zero = layoutSystemBlocks({ | ||
| ...common, | ||
| runtime: { | ||
| default_cache_ttl: '5m', | ||
| persona_preset: 'zero', | ||
| system_layout: 'zero', | ||
| }, | ||
| }) | ||
| expect(zero[0]).toContain(`prompt_version=${COMPACT_IDENTITY}`) | ||
| expect(zero).not.toContain(IDENTITY) | ||
|
|
||
| const official = layoutSystemBlocks({ | ||
| ...common, | ||
| runtime: { | ||
| default_cache_ttl: '1h', | ||
| persona_preset: 'official', | ||
| system_layout: 'identity', | ||
| }, | ||
| }) | ||
| expect(official).toContain(IDENTITY) | ||
| expect(official).not.toContain('# Environment\nTime zone:') | ||
|
|
||
| const full = layoutSystemBlocks({ | ||
| ...common, | ||
| officialFullPrompt: [ | ||
| 'official static prompt', | ||
| '# Environment\nTime zone: UTC', | ||
| ], | ||
| runtime: { | ||
| default_cache_ttl: '1h', | ||
| persona_preset: 'official_full', | ||
| system_layout: 'identity', | ||
| }, | ||
| }) | ||
| expect(full).toContain( | ||
| 'official static prompt\n\n# Environment\nTime zone: UTC', | ||
| ) | ||
| }) | ||
|
|
||
| test('removes turn-scoped attribution fields from the native cache prefix', () => { | ||
| const blocks = layoutSystemBlocks({ | ||
| attribution: | ||
| 'x-anthropic-billing-header: cc_version=test; cch=abcdef; cc_workload=cron;', | ||
| runtime: { | ||
| default_cache_ttl: '1h', | ||
| persona_preset: 'official', | ||
| system_layout: 'identity', | ||
| }, | ||
| }) | ||
| expect(blocks[0]).toBe('x-anthropic-billing-header: cc_version=test;') | ||
| }) | ||
| }) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Use one predicate for native slot mode. Several gates check whether
CLAUDE_CODE_KIN_NATIVE_SLOTSis truthy.nativeSlotCount()parses the value and returns 0 for"0"or non-numeric values. With such a value, stdin is not read, MCP servers are not connected, and the normal headless flow is skipped. The native loop then exits at once, so the process does no work and gives no error. ExportisNativeSlotMode = () => nativeSlotCount() > 0and use it at every gate.src/cli/print.ts#L501-L501: replace the truthy check withisNativeSlotMode().src/main.tsx#L1036-L1036: replace the truthy check ingetInputPromptwithisNativeSlotMode().src/main.tsx#L3279-L3279: replace the truthy check that skips MCP connection with!isNativeSlotMode().src/kin/systemLayout.ts#L17-L19: replace the truthy checks ingetSystemLayoutandgetKinTimezonewithisNativeSlotMode().📍 Affects 3 files
src/cli/print.ts#L501-L501(this comment)src/main.tsx#L1036-L1036src/main.tsx#L3279-L3279src/kin/systemLayout.ts#L17-L19🤖 Prompt for AI Agents