* fix(agent-claude-code): map bookkeeping JSONL types to ready, not active Claude writes several types AFTER finishing a turn — `file-history-snapshot`, `attachment`, `pr-link`, `queue-operation`, `permission-mode`, `last-prompt`, `ai-title`, `agent-color`, `agent-name`, `custom-title`. Until now they fell through to the `default` switch branch and looked `active` for 30s, making finished sessions appear busy. This was almost certainly the root cause of "Claude looks like it's still working when it's done" reports (the #1908 family). Add explicit cases mapping each to `ready`/`idle` by age, same as `assistant`/`summary`. Three existing tests that asserted these returned `active` were testing the buggy behavior — updated to the correct behavior, plus four new tests covering `attachment`, `permission-mode`, and `ai-title`. * fix(agent-claude-code): broaden process regex to match real install variants `(?:^|\/)claude(?:\s|$)` rejected several legitimate Claude installs, causing AO to declare sessions `exited` while Claude was still running: - `claude-code` (some Anthropic CLI variants) - `claude.exe` / `claude.js` / `claude.cjs` (Windows / shim installs) - `.claude` (dot-prefix shim) - `node /opt/.../@anthropic-ai/claude-code/cli.js` (npm shim — path-contains-claude case) New regex `(?:^|\/)(?:\.)?claude(?:[-.][\w-]+)*(?:[\s/]|$)` allows optional dot prefix, hyphen/dot suffix chains, and `/` terminator (for paths with claude as a path component). Still anchored at `/` or start-of-line so `claudia`/`claudine` etc. don't false-match. Tests: 7-case `it.each` for each install shape returning true, dedicated rejection test for `claudia`/`claudine`. Old strict test (`does not match claude-code`) deleted — it was testing the bug. * fix(agent-claude-code): warn on non-ENOENT failures reading ~/.claude/projects/ Previously `findLatestSessionFile` swallowed every readdir error silently, so a permission-denied or fd-exhausted misconfig on `~/.claude/projects/<slug>` would leave the session looking permanently `idle` on the dashboard with zero telemetry — debugging this took hours in the past (filed as one of the gotchas in #1927's description). Now: ENOENT (dir hasn't been created yet) stays silent because that's normal during the early lifecycle. Every other errno (EACCES, EPERM, EMFILE, etc.) emits a single console.warn naming the dir and the errno, plus noting that activity will fall back to the AO JSONL only. Caller behavior unchanged (still returns null). Tests: one for EACCES (via chmod 0o000) confirming warn fires with the errno, one for the missing-dir case confirming warn does NOT fire. * fix(agent-claude-code): resolve symlinked workspace paths before slugifying If AO records `session.workspacePath` as a symlink (e.g. `/Users/me/symlinks/repo`) and Claude resolves the target before computing its on-disk slug (e.g. `/Users/me/code/repo`), the two slugs diverge and `findLatestSessionFile` looks in an empty `~/.claude/projects/<wrong-slug>/` dir forever. Session looks permanently `idle` on the dashboard with no telemetry. Add `resolveWorkspaceForClaude(workspacePath)` (try realpathSync, fall back to literal on error). Use it in all three sites that slugify a workspace path: `getClaudeActivityState`, `getSessionInfo`, `getRestoreCommand`. Re-exported from `index.ts` for downstream consumers. Kept `toClaudeProjectPath` as a pure string transform — the realpath happens in the caller, so the slug function stays trivially testable. New test confirms: write JSONL under the target's slug, call getActivityState with the symlink path, expect `ready` (was `null` before the fix). Test-setup also needed `realpathSync(mkdtempSync(...))` because /var/folders is itself a symlink on macOS, otherwise existing tests would set up JSONL under one slug and the code would now look under another. * fix(agent-claude-code): disambiguate multi-session via claudeSessionUuid When two Claude sessions are running in the same workspace, `findLatestSessionFile` picked newest-mtime — which is the WRONG session's JSONL whenever its sibling has just written. The `getSessionInfo` method already captures `session.metadata.claudeSessionUuid` (the UUID Claude uses as its JSONL filename), but `getClaudeActivityState` was ignoring it. `findLatestSessionFile` now accepts an optional `preferredUuid`. If provided, it checks `<projectDir>/<preferredUuid>.jsonl` first via `stat()` and returns that path on success. Falls back to newest-mtime when the UUID is missing or the named file doesn't exist yet (fresh session not yet introspected, or file rotated/removed). `getClaudeActivityState` reads `session.metadata.claudeSessionUuid` (coerces unknown→string, trims, treats empty as undefined) and passes it through. Tests: - prefers UUID-named JSONL when set, ignoring newer sibling file - falls back to newest-mtime when UUID-named file doesn't exist * chore: changeset for claude-activity-edge-cases * fix(agent-claude-code): use ES import for realpathSync in test setup CI lint rejected the `require()` form. Move realpathSync to the top-level node:fs import. * refactor(agent-claude-code): drop dead tool_use and result switch cases Both types were inherited from the Agent-interface spec but never actually emitted by Claude (verified on disk for #1927). They now fall to the `default` branch. Behavior change: - `tool_use` → previously in user/progress branch → identical to default. No real-world effect. - `result` → previously in assistant/summary branch (never active) → now default (can be active when fresh). No real-world effect because Claude doesn't emit `result`. If Claude ever introduces these types and we want different semantics, we add explicit cases back. Until then, the dead switch entries were noise. Tests: - Removed `returns 'active' for recent 'tool_use' entry` (default branch does the same) - Removed `returns 'ready' for recent 'result' entry` (was testing dead behavior) - Added `unknown types fall through to default branch — fresh → active` to lock the default-branch semantics for any future Claude type addition. * feat(agent-claude-code): detect blocked from terminal regex too Until now `blocked` was only sourced from native JSONL (`{type:"system", subtype:"api_error", level:"error"}` from #1927). The terminal-regex pipeline (`detectActivity` → `recordTerminalActivity` → AO activity-JSONL → `checkActivityLogState`) only emitted `waiting_input`/`idle`/`active`, never `blocked`. So when Claude's native JSONL was unreadable (slug drift, fresh session pre-introspection, etc.) the blocked state was unreachable via the safety net. Patterns observed empirically by capturing tmux output during a real api.anthropic.com block (api blocked via /etc/hosts, fresh `ao spawn` session, `tmux capture-pane` after retries started): ⎿ Unable to connect to API (ConnectionRefused) Retrying in 19s · attempt 7/10 Add two matches to `classifyTerminalOutput`: - /Unable to connect to API/i — primary error wording - /Retrying in \\d+s.*attempt \\d+\\/\\d+/i — retry counter (fires even when error scrolled off) Placed BEFORE the existing waiting_input checks because Claude's static UI footer contains `bypass permissions on (shift+tab to cycle)` which the existing `bypass.*permissions` regex matches — without this ordering, a real blocked state would lose to that incidental match. Tests cover: real captured output, alternate error code (FailedToOpenSocket), retry counter alone, and the bypass-permissions-footer precedence case. * fix(agent-claude-code): address review feedback on PR #1932 Three issues raised by @greptile-apps review: 1. console.warn flooded on every poll (P1). getClaudeActivityState runs on a polling interval — without a dedupe, a single EACCES path would log 60+ lines/minute indefinitely. Added module-level Set<string> keyed by projectDir; warn only on first miss per path for the process lifetime. Exported resetWarnedReaddirPaths for test isolation. 2. realpathSync blocked the event loop in fully-async callers (P2). Switched to async realpath from node:fs/promises. Updated the three call sites (getClaudeActivityState, getSessionInfo, getRestoreCommand) to await. resolveWorkspaceForClaude is now Promise<string>. 3. Test regex /failed to read.*EACCES|EPERM/ parsed as (failed to read.*EACCES) | (EPERM) because | has lowest precedence. Wrapped the alternation in a non-capturing group: (?:EACCES|EPERM). Also added a new test confirming the dedupe works: three consecutive polls against an EACCES path produce exactly one warn. * fix(agent-claude-code): skip UI-noise JSONL types when reading last entry Regression caused by the earlier bookkeeping-types fix on this branch (commit |
||
|---|---|---|
| .. | ||
| README.md | ||
| activity-events-webhooks-mux.md | ||
| claude-activity-detection-split.md | ||
| claude-activity-edge-cases.md | ||
| cli-activity-events.md | ||
| config.json | ||
| fix-done-bar-scroll.md | ||
| issue-1660-recovery-metadata-events.md | ||
| launch-orchestrator-clean.md | ||
| linear-transient-retry.md | ||
| notifier-release-links.md | ||
| quiet-sqlite-rebuild.md | ||
| restore-button-pr-merged.md | ||
README.md
Changesets
Hello and welcome! This folder has been automatically generated by @changesets/cli, a build tool that works
with multi-package repos, or single-package repos to help you version and publish your code. You can
find the full documentation for it in our repository
We have a quick list of common questions to get you started engaging with this project in
our documentation