Skip to content
Merged
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 bin/chat
Original file line number Diff line number Diff line change
Expand Up @@ -286,7 +286,7 @@ def too_long(line):

def ask(first):
"""One compaction: a Haiku session that gets the task, and as many size retries as it needs."""
env = {k: v for k, v in os.environ.items() if not k.startswith(("HEX_", "CLAUDECODE"))}
env = {**{k: v for k, v in os.environ.items() if not k.startswith(("HEX_", "CLAUDECODE"))}, "CLAUDE_CODE_ENTRYPOINT": "hex"}
proc = subprocess.Popen(
[CLAUDE, "-p", "--safe-mode", "--model", "haiku", "--effort", "high", "--tools", "",
"--system-prompt-file", str(PROMPT), "--input-format", "stream-json",
Expand Down
2 changes: 2 additions & 0 deletions bin/session
Original file line number Diff line number Diff line change
Expand Up @@ -67,4 +67,6 @@ fi
# With several Claude accounts pooled by tokenmaxxing (github.com/anaclumos/tokenmaxxing), each session starts on the one with the most headroom left.
[ -z "${CLAUDE_SECURESTORAGE_CONFIG_DIR+set}" ] && store=$(tokenmaxxing seat $$ 2>/dev/null) && export CLAUDE_SECURESTORAGE_CONFIG_DIR="$store"
export CLAUDE_CODE_ADDITIONAL_DIRECTORIES_CLAUDE_MD=1
# Anthropic's 5-hour limit charges a run with no terminal, which Claude Code labels sdk-cli, more per token than an interactive one (anthropics/claude-code#97074), and Claude Code reads its label only from the environment, where a labeled parent leaves its own.
if [ "$bg" != --bg ] && [ ! -t 1 ]; then export CLAUDE_CODE_ENTRYPOINT=hex; else unset CLAUDE_CODE_ENTRYPOINT; fi
Comment thread
AVGVSTVS96 marked this conversation as resolved.
Resolved
exec claude --plugin-dir "$hex/plugin" "$@"
3 changes: 2 additions & 1 deletion channels/voice/check.ts
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,8 @@ function claude(input: string, hexDir: string, state: string) {
writeFileSync(join(cwd, 'messages.md'), messages(hexDir))
writeFileSync(join(cwd, 'threads.json'), JSON.stringify(store(state).threads(), null, 1))
return new Promise<Verdict>((resolve, reject) => {
const child = spawn('claude', args, { cwd, stdio: ['pipe', 'pipe', 'pipe'] })
const env = { ...Object.fromEntries(Object.entries(process.env).filter(([name]) => !name.startsWith('HEX_') && !name.startsWith('CLAUDECODE'))), CLAUDE_CODE_ENTRYPOINT: 'hex' }
const child = spawn('claude', args, { cwd, env, stdio: ['pipe', 'pipe', 'pipe'] })
let out = ''
let err = ''
child.stdout.on('data', chunk => (out += chunk))
Expand Down
5 changes: 3 additions & 2 deletions hub/claude.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,9 @@ export type Usage = { sent: number; at?: number; short?: boolean }
const home = () => process.env.CLAUDE_CONFIG_DIR ?? join(homedir(), '.claude')
const short = (session: string) => session.slice(0, 8)

// What a Claude process the hub starts never inherits from it: the bots' tokens and hex's own settings.
const PRIVATE = /_BOT_TOKEN$|^HEX_|^BUZZ_/
// What a Claude process the hub starts never inherits from it: the bots' tokens, hex's own settings, and the
// label of whatever run started the hub, which Claude Code would report for every session its daemon starts.
const PRIVATE = /_BOT_TOKEN$|^HEX_|^BUZZ_|^CLAUDE_CODE_ENTRYPOINT$/
Comment thread
AVGVSTVS96 marked this conversation as resolved.
Resolved

export function inherited(): Record<string, string | undefined> {
return Object.fromEntries(Object.entries(process.env).filter(([name]) => !PRIVATE.test(name)))
Expand Down
2 changes: 1 addition & 1 deletion hub/observe.ts
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ const escape = (text: string) => text.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
// One call with no tools: its only effect is what it returns.
// Its three tries end inside the pass's lease (4 calls long), so a hung call fails rather than racing the next pass.
async function call(input: string, system: string, args: string[]) {
const env = Object.fromEntries(Object.entries(process.env).filter(([name]) => !name.startsWith('HEX_') && !name.startsWith('CLAUDECODE')))
const env = { ...Object.fromEntries(Object.entries(process.env).filter(([name]) => !name.startsWith('HEX_') && !name.startsWith('CLAUDECODE'))), CLAUDE_CODE_ENTRYPOINT: 'hex' }
const run = Bun.spawn(
['claude', '-p', '--safe-mode', '--model', MODEL, ...args, '--system-prompt', system, '--output-format', 'json', '--no-session-persistence'],
{ cwd: hexDir(), env, stdin: new Blob([input]), stdout: 'pipe', stderr: 'pipe', timeout: CALL },
Expand Down
15 changes: 15 additions & 0 deletions plugin/tests/session.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -150,3 +150,18 @@ test('every folder a background session starts in is trusted (a -p run never ask
const args: string[] = JSON.parse(readFileSync(join(home, 'launched'), 'utf8'))
expect(JSON.parse(args[args.indexOf('--settings') + 1]!)).toMatchObject({ model: 'opus', permissions: { deny: [`Read(/${home}/.env)`] } })
})

test('a run with no terminal says it is hex, over the label it inherited, and a background session drops it for its own', () => {
writeFileSync(join(home, 'fake/claude'), `#!/bin/sh\nprintf '%s' "\${CLAUDE_CODE_ENTRYPOINT:-unset}" > "${home}/entrypoint"\n`)
const env = { ...process.env, CLAUDE_CODE_ENTRYPOINT: 'sdk-cli' }
const launched = (...args: string[]) => {
rmSync(join(home, 'entrypoint'), { force: true })
expect(Bun.spawnSync([join(home, '.hex/bin/session'), ...args], { cwd: home, env }).exitCode).toBe(0)
return readFileSync(join(home, 'entrypoint'), 'utf8')
}
expect(launched('-p', 'build it')).toBe('hex')
expect(launched('build it')).toBe('hex')
expect(launched('-p', '--', '--bg')).toBe('hex')
expect(launched('--bg', 'build it')).toBe('unset')
expect(launched('--bg', '-p')).toBe('unset')
})