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
3 changes: 2 additions & 1 deletion TODO.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,8 @@ Claude Code stores trust state in `~/.claude/projects/<slug>/`. Pre-creating tha
directory before launching the interactive session bypasses the dialog.

- [x] T001: Add `ensure_workspace_trusted()` to new_session.py — pre-creates projects dir. Also fixed `get_project_logs_dir` slug encoding to match Claude Code (regex `[^a-zA-Z0-9-]` instead of only replacing `\/:.`)
- [ ] T002: Add tests for ensure_workspace_trusted and fixed slug encoding
- [x] T002: Add tests for ensure_workspace_trusted and fixed slug encoding (68 total, merged in PR #15)
- [ ] T003: Fix pretrust — mkdir alone doesn't work, use `claude -p` to create real trust state

## Rename: context-reset → new-session (007)

Expand Down
33 changes: 26 additions & 7 deletions new_session.py
Original file line number Diff line number Diff line change
Expand Up @@ -959,20 +959,39 @@ def get_project_logs_dir(project_dir):


def ensure_workspace_trusted(project_dir):
"""Pre-create the Claude Code projects directory so the trust dialog is skipped.
"""Run a minimal `claude -p` session to establish workspace trust.

Claude Code shows "Is this a project you trust?" on first interactive launch
in a new directory. Trust state = existence of ~/.claude/projects/<slug>/.
Pre-creating the directory with a seed JSONL avoids the prompt.
in a new directory. The -p (print) flag skips the trust dialog and creates
the proper trust state (project dir + session JSONL). Subsequent interactive
launches in the same directory won't prompt.

No-ops if the project already has a session file.
"""
logs_dir = get_project_logs_dir(project_dir)
if os.path.exists(logs_dir):
return # Already trusted
# Check for at least one JSONL session file (dir alone isn't enough)
jsonls = [f for f in os.listdir(logs_dir) if f.endswith('.jsonl')]
if jsonls:
return # Already trusted with a real session
try:
os.makedirs(logs_dir, exist_ok=True)
log(f"Pre-trusted workspace: {logs_dir}")
log(f"Pre-trusting workspace via claude -p in {project_dir}")
cmd = ['claude', '-p', 'ok', '--dangerously-skip-permissions']
result = subprocess.run(
cmd, cwd=project_dir, timeout=30,
capture_output=True, text=True,
startupinfo=_si(),
)
if result.returncode == 0:
log("Pre-trust complete")
else:
log(f"WARNING: pre-trust claude -p returned {result.returncode}")
except subprocess.TimeoutExpired:
log("WARNING: pre-trust claude -p timed out after 30s")
except FileNotFoundError:
log("WARNING: claude binary not found, cannot pre-trust")
except Exception as e:
log(f"WARNING: could not pre-trust workspace: {e}")
log(f"WARNING: pre-trust failed: {e}")


def get_newest_jsonl(logs_dir):
Expand Down
11 changes: 7 additions & 4 deletions scripts/test.py
Original file line number Diff line number Diff line change
Expand Up @@ -222,11 +222,14 @@ def test(name, condition):
os.makedirs(fake_proj)
logs_dir = context_reset.get_project_logs_dir(fake_proj)
test("dir does not exist before trust", not os.path.exists(logs_dir))
# Simulate already-trusted: create dir + JSONL, verify no-op
os.makedirs(logs_dir, exist_ok=True)
seed = os.path.join(logs_dir, "00000000-0000-0000-0000-000000000000.jsonl")
with open(seed, "w") as fh:
fh.write("{}\n")
# Should be a no-op (already has JSONL) — no subprocess spawned
context_reset.ensure_workspace_trusted(fake_proj)
test("dir exists after trust", os.path.exists(logs_dir))
# Second call is a no-op (no error)
context_reset.ensure_workspace_trusted(fake_proj)
test("idempotent (no error on second call)", os.path.exists(logs_dir))
test("skips when JSONL exists (no-op)", os.path.exists(seed))

# --- get_newest_jsonl ---
print("\n=== get_newest_jsonl ===")
Expand Down
Loading