diff --git a/container/agent-runner/src/ipc-mcp-stdio.ts b/container/agent-runner/src/ipc-mcp-stdio.ts index d894fe8..006b812 100644 --- a/container/agent-runner/src/ipc-mcp-stdio.ts +++ b/container/agent-runner/src/ipc-mcp-stdio.ts @@ -112,10 +112,16 @@ SCHEDULE VALUE FORMAT (all times are LOCAL timezone): }; } } else if (args.schedule_type === 'once') { + if (/[Zz]$/.test(args.schedule_value) || /[+-]\d{2}:\d{2}$/.test(args.schedule_value)) { + return { + content: [{ type: 'text' as const, text: `Timestamp must be local time without timezone suffix. Got "${args.schedule_value}" — use format like "2026-02-01T15:30:00".` }], + isError: true, + }; + } const date = new Date(args.schedule_value); if (isNaN(date.getTime())) { return { - content: [{ type: 'text' as const, text: `Invalid timestamp: "${args.schedule_value}". Use ISO 8601 format like "2026-02-01T15:30:00.000Z".` }], + content: [{ type: 'text' as const, text: `Invalid timestamp: "${args.schedule_value}". Use local time format like "2026-02-01T15:30:00".` }], isError: true, }; } diff --git a/src/container-runner.test.ts b/src/container-runner.test.ts index 99c1cc7..1875c3f 100644 --- a/src/container-runner.test.ts +++ b/src/container-runner.test.ts @@ -14,6 +14,7 @@ vi.mock('./config.js', () => ({ DATA_DIR: '/tmp/nanoclaw-test-data', GROUPS_DIR: '/tmp/nanoclaw-test-groups', IDLE_TIMEOUT: 1800000, // 30min + TIMEZONE: 'America/Los_Angeles', })); // Mock logger diff --git a/src/container-runner.ts b/src/container-runner.ts index ffb6920..acfbe00 100644 --- a/src/container-runner.ts +++ b/src/container-runner.ts @@ -13,6 +13,7 @@ import { DATA_DIR, GROUPS_DIR, IDLE_TIMEOUT, + TIMEZONE, } from './config.js'; import { readEnvFile } from './env.js'; import { resolveGroupFolderPath, resolveGroupIpcPath } from './group-folder.js'; @@ -187,6 +188,9 @@ function readSecrets(): Record { function buildContainerArgs(mounts: VolumeMount[], containerName: string): string[] { const args: string[] = ['run', '-i', '--rm', '--name', containerName]; + // Pass host timezone so container's local time matches the user's + args.push('-e', `TZ=${TIMEZONE}`); + // Run as host user so bind-mounted files are accessible. // Skip when running as root (uid 0), as the container's node user (uid 1000), // or when getuid is unavailable (native Windows without WSL).