Skip to content
Closed
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
3 changes: 1 addition & 2 deletions brain.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
import logging
import os
import re
import shlex
import shutil
import time
from dataclasses import dataclass, field
Expand Down Expand Up @@ -742,7 +741,7 @@ def launch_prompt(self) -> str:

def command(self) -> list[str]:
c = self.config
cmd = shlex.split(self._claude) + [
cmd = claude_env.split_command(self._claude) + [
"-p", "--input-format", "stream-json", "--output-format", "stream-json",
"--verbose", "--include-partial-messages",
"--model", c.model, "--effort", c.effort, "--name", "jarvis",
Expand Down
13 changes: 13 additions & 0 deletions claude_env.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
from __future__ import annotations

import os
import shlex

# Every ANTHROPIC_* variable, not just the key: the base URL and the model
# override redirect a child just as effectively as credentials do.
Expand Down Expand Up @@ -56,3 +57,15 @@ def child_env(base: dict[str, str] | None = None) -> dict[str, str]:
return {k: v for k, v in source.items()
if not k.startswith(SCRUBBED_ENV_PREFIXES)
and k not in SCRUBBED_ENV_KEYS}


def split_command(command: str) -> list[str]:
r"""The argv for a configured `claude` command, which may carry arguments.

A path to an existing file is taken whole: POSIX `shlex.split` reads the
backslashes in a Windows path (`C:\Users\...\claude.exe`, exactly what
`shutil.which` returns there) as escapes and drops them, leaving a path
that does not exist."""
if os.path.isfile(command):
return [command]
return shlex.split(command)
26 changes: 26 additions & 0 deletions data_paths.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import json
import logging
import os
import sys
import tempfile
import time
from pathlib import Path
Expand Down Expand Up @@ -386,6 +387,9 @@ def ensure_tool_token() -> str:
os.close(fd)
return token

if sys.platform == "win32":
return _adopt_tool_token_windows(path, token)

fd = os.open(str(path), os.O_RDWR | os.O_NOFOLLOW)
try:
info = os.fstat(fd)
Expand All @@ -405,3 +409,25 @@ def ensure_tool_token() -> str:
return token
finally:
os.close(fd)


def _adopt_tool_token_windows(path: Path, token: str) -> str:
"""Windows has no O_NOFOLLOW, getuid or POSIX modes, so the fd-based
checks above cannot run. Refuse links and non-regular files via lstat;
access control is left to the user profile's ACLs, which already keep
the data directory private to this account."""
import stat as _stat
info = os.lstat(path)
if _stat.S_ISLNK(info.st_mode) or not _stat.S_ISREG(info.st_mode):
raise OSError(f"{path} is not a regular file")
fd = os.open(str(path), os.O_RDWR | getattr(os, "O_BINARY", 0))
try:
existing = os.read(fd, 4096).decode("utf-8", "ignore").strip()
if existing:
return existing
os.ftruncate(fd, 0)
os.lseek(fd, 0, os.SEEK_SET)
os.write(fd, token.encode("utf-8"))
return token
finally:
os.close(fd)
3 changes: 1 addition & 2 deletions run_executor.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
import logging
import math
import os
import shlex
import shutil
import time
from typing import Callable
Expand Down Expand Up @@ -441,7 +440,7 @@ async def wait_for(self, run_id: str, timeout: float = 30) -> dict:

def _command(self, run_id: str, resume_from: str | None,
model: str | None = None) -> list[str]:
base = shlex.split(self._claude_path)
base = claude_env.split_command(self._claude_path)
cmd = base + ["-p", "--output-format", "stream-json", "--verbose",
"--session-id", run_id]
if resume_from:
Expand Down
15 changes: 12 additions & 3 deletions usage_scan.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,9 +65,10 @@
import json
import logging
import os
import sys
import time
from dataclasses import dataclass, field
from datetime import datetime
from datetime import datetime, timezone
from pathlib import Path

import session_watch
Expand Down Expand Up @@ -158,8 +159,16 @@ def _tokens_from(usage) -> Tokens:
# `fromisoformat` accepts that string happily, so catching only its ValueError
# left the second call unguarded. Two years of slack at each end covers every
# UTC offset without needing to know the local one.
_DAY_MIN = datetime(2, 1, 1).timestamp()
_DAY_MAX = datetime(9997, 1, 1).timestamp()
#
# The bounds are built UTC-aware: a naive datetime's `.timestamp()` goes
# through the platform's local-time conversion, which on Windows raises
# OSError for anything before 1970 — at import time. Windows'
# `fromtimestamp` refuses negative epochs for the same reason, so there the
# floor is the epoch itself.
_DAY_MIN = datetime(2, 1, 1, tzinfo=timezone.utc).timestamp()
if sys.platform == "win32":
_DAY_MIN = 0.0
_DAY_MAX = datetime(9997, 1, 1, tzinfo=timezone.utc).timestamp()


def _epoch(stamp) -> float | None:
Expand Down