From c44eae0b1727ae368d01df006ffd5d4331f456c9 Mon Sep 17 00:00:00 2001 From: weishu Date: Sat, 17 Jan 2026 19:36:35 +0800 Subject: [PATCH] fix: use --add-dir flag for granting Claude read access to uploads --- cli/src/claude/claudeLocal.ts | 5 +++++ cli/src/claude/claudeRemote.ts | 2 ++ cli/src/claude/sdk/query.ts | 2 ++ cli/src/claude/sdk/types.ts | 1 + cli/src/constants/uploadPaths.ts | 8 ++++++++ cli/src/modules/common/handlers/uploads.ts | 10 ++++++---- 6 files changed, 24 insertions(+), 4 deletions(-) create mode 100644 cli/src/constants/uploadPaths.ts diff --git a/cli/src/claude/claudeLocal.ts b/cli/src/claude/claudeLocal.ts index 60b800fd..b99964d4 100644 --- a/cli/src/claude/claudeLocal.ts +++ b/cli/src/claude/claudeLocal.ts @@ -7,6 +7,7 @@ import { appendMcpConfigArg } from "./utils/mcpConfig"; import { systemPrompt } from "./utils/systemPrompt"; import { withBunRuntimeEnv } from "@/utils/bunRuntime"; import { spawnWithAbort } from "@/utils/spawnWithAbort"; +import { getHapiBlobsDir } from "@/constants/uploadPaths"; export async function claudeLocal(opts: { abort: AbortSignal, @@ -68,6 +69,10 @@ export async function claudeLocal(opts: { args.push('--settings', opts.hookSettingsPath); logger.debug(`[ClaudeLocal] Using hook settings: ${opts.hookSettingsPath}`); + // Add blobs directory for file upload access + args.push('--add-dir', getHapiBlobsDir()); + logger.debug(`[ClaudeLocal] Adding blobs directory: ${getHapiBlobsDir()}`); + // Prepare environment variables // Note: Local mode uses global Claude installation const env = { diff --git a/cli/src/claude/claudeRemote.ts b/cli/src/claude/claudeRemote.ts index cca30820..85309c1e 100644 --- a/cli/src/claude/claudeRemote.ts +++ b/cli/src/claude/claudeRemote.ts @@ -9,6 +9,7 @@ import { getProjectPath } from "./utils/path"; import { awaitFileExist } from "@/modules/watcher/awaitFileExist"; import { systemPrompt } from "./utils/systemPrompt"; import { PermissionResult } from "./sdk/types"; +import { getHapiBlobsDir } from "@/constants/uploadPaths"; export async function claudeRemote(opts: { @@ -123,6 +124,7 @@ export async function claudeRemote(opts: { abort: opts.signal, pathToClaudeCodeExecutable: 'claude', settingsPath: opts.hookSettingsPath, + additionalDirectories: [getHapiBlobsDir()], } // Track thinking state diff --git a/cli/src/claude/sdk/query.ts b/cli/src/claude/sdk/query.ts index 7f1af488..8cf3bf88 100644 --- a/cli/src/claude/sdk/query.ts +++ b/cli/src/claude/sdk/query.ts @@ -260,6 +260,7 @@ export function query(config: { const { prompt, options: { + additionalDirectories = [], allowedTools = [], appendSystemPrompt, customSystemPrompt, @@ -303,6 +304,7 @@ export function query(config: { if (settingsPath) args.push('--settings', settingsPath) if (allowedTools.length > 0) args.push('--allowedTools', allowedTools.join(',')) if (disallowedTools.length > 0) args.push('--disallowedTools', disallowedTools.join(',')) + if (additionalDirectories.length > 0) args.push('--add-dir', ...additionalDirectories) if (strictMcpConfig) args.push('--strict-mcp-config') if (permissionMode) args.push('--permission-mode', permissionMode) diff --git a/cli/src/claude/sdk/types.ts b/cli/src/claude/sdk/types.ts index d313cc73..59a197be 100644 --- a/cli/src/claude/sdk/types.ts +++ b/cli/src/claude/sdk/types.ts @@ -157,6 +157,7 @@ export interface CanCallToolCallback { */ export interface QueryOptions { abort?: AbortSignal + additionalDirectories?: string[] allowedTools?: string[] appendSystemPrompt?: string customSystemPrompt?: string diff --git a/cli/src/constants/uploadPaths.ts b/cli/src/constants/uploadPaths.ts new file mode 100644 index 00000000..46dc664c --- /dev/null +++ b/cli/src/constants/uploadPaths.ts @@ -0,0 +1,8 @@ +import { join } from 'path' +import { tmpdir } from 'os' + +export const HAPI_BLOBS_DIR_NAME = 'hapi-blobs' + +export function getHapiBlobsDir(): string { + return join(tmpdir(), HAPI_BLOBS_DIR_NAME) +} diff --git a/cli/src/modules/common/handlers/uploads.ts b/cli/src/modules/common/handlers/uploads.ts index cd867594..acd67f2d 100644 --- a/cli/src/modules/common/handlers/uploads.ts +++ b/cli/src/modules/common/handlers/uploads.ts @@ -1,10 +1,10 @@ import { logger } from '@/ui/logger' -import { mkdtemp, rm, writeFile } from 'fs/promises' +import { mkdir, mkdtemp, rm, writeFile } from 'fs/promises' import { join, resolve, sep } from 'path' -import { tmpdir } from 'os' import { rmSync } from 'node:fs' import type { RpcHandlerManager } from '@/api/rpc/RpcHandlerManager' import { getErrorMessage, rpcError } from '../rpcResponses' +import { getHapiBlobsDir } from '@/constants/uploadPaths' interface UploadFileRequest { sessionId?: string @@ -74,7 +74,9 @@ async function getOrCreateUploadDir(sessionId?: string): Promise { const safeKey = sanitizeFilename(sessionKey) const creation = (async () => { try { - const dir = await mkdtemp(join(tmpdir(), `hapi-uploads-${safeKey}-`)) + const blobsDir = getHapiBlobsDir() + await mkdir(blobsDir, { recursive: true }) + const dir = await mkdtemp(join(blobsDir, `${safeKey}-`)) if (uploadDirCleanupRequested.has(sessionKey)) { try { await rm(dir, { recursive: true, force: true }) @@ -151,7 +153,7 @@ function isPathWithinUploadDir(path: string, sessionId?: string): boolean { } const safeKey = sanitizeFilename(sessionKey) - const resolvedPrefix = resolve(tmpdir(), `hapi-uploads-${safeKey}-`) + const resolvedPrefix = resolve(getHapiBlobsDir(), `${safeKey}-`) return resolvedPath.startsWith(resolvedPrefix) }