fix: prevent race condition marking new sessions as 'killed'

When a session is freshly created, the lifecycle worker may poll before
the claude process has started in the tmux session. findClaudeProcess()
returns null, getActivityState() reports "exited", and the session gets
marked as "killed" even though it's still starting up.

Add a 60-second startup grace period: if the agent process is not found
but the session was created less than 60 seconds ago, return "idle"
instead of "exited" to give the agent time to launch.

Fixes #989
This commit is contained in:
Prateek 2026-04-07 20:59:57 +05:30
parent c71a302822
commit 5e4244a880
27 changed files with 47 additions and 28 deletions

View File

@ -1,6 +1,6 @@
{
"name": "@aoagents/ao",
"version": "0.2.2",
"version": "0.2.4",
"description": "Orchestrate parallel AI coding agents — global CLI wrapper",
"license": "MIT",
"type": "module",

View File

@ -1,6 +1,6 @@
{
"name": "@aoagents/ao-cli",
"version": "0.2.2",
"version": "0.2.4",
"description": "CLI for agent-orchestrator — the `ao` command",
"license": "MIT",
"type": "module",

View File

@ -1,6 +1,6 @@
{
"name": "@aoagents/ao-core",
"version": "0.2.0",
"version": "0.2.2",
"description": "Core library — types, config, session manager, lifecycle manager, event bus",
"license": "MIT",
"type": "module",

View File

@ -1,6 +1,6 @@
{
"name": "@aoagents/ao-plugin-agent-aider",
"version": "0.2.0",
"version": "0.2.2",
"description": "Agent plugin: Aider",
"license": "MIT",
"type": "module",

View File

@ -1,6 +1,6 @@
{
"name": "@aoagents/ao-plugin-agent-claude-code",
"version": "0.2.0",
"version": "0.2.2",
"description": "Agent plugin: Claude Code",
"license": "MIT",
"type": "module",

View File

@ -116,10 +116,19 @@ describe("Claude Code Activity Detection", () => {
// Process / handle edge cases
// -----------------------------------------------------------------------
it("returns 'exited' when process is not running", async () => {
it("returns 'idle' during startup grace period when process is not yet running", async () => {
vi.spyOn(agent, "isProcessRunning").mockResolvedValue(false);
writeJsonl([{ type: "assistant" }]);
expect((await agent.getActivityState(makeSession()))?.state).toBe("exited");
// Session just created — within 60s grace period, so agent may still be starting
expect((await agent.getActivityState(makeSession()))?.state).toBe("idle");
});
it("returns 'exited' when process is not running and session is old", async () => {
vi.spyOn(agent, "isProcessRunning").mockResolvedValue(false);
writeJsonl([{ type: "assistant" }]);
// Session created 2 minutes ago — well past grace period
const oldSession = makeSession({ createdAt: new Date(Date.now() - 120_000) });
expect((await agent.getActivityState(oldSession))?.state).toBe("exited");
});
it("returns 'exited' when no runtimeHandle", async () => {

View File

@ -720,7 +720,17 @@ function createClaudeCodeAgent(): Agent {
const exitedAt = new Date();
if (!session.runtimeHandle) return { state: "exited", timestamp: exitedAt };
const running = await this.isProcessRunning(session.runtimeHandle);
if (!running) return { state: "exited", timestamp: exitedAt };
if (!running) {
// Grace period: don't report "exited" for freshly created sessions
// where the agent process may not have launched yet (race between
// tmux session creation and the agent binary starting).
const STARTUP_GRACE_MS = 60_000;
const ageMs = Date.now() - session.createdAt.getTime();
if (ageMs < STARTUP_GRACE_MS) {
return { state: "idle", timestamp: session.createdAt };
}
return { state: "exited", timestamp: exitedAt };
}
// Process is running - check JSONL session file for activity
if (!session.workspacePath) {

View File

@ -1,6 +1,6 @@
{
"name": "@aoagents/ao-plugin-agent-codex",
"version": "0.2.0",
"version": "0.2.2",
"description": "Agent plugin: OpenAI Codex CLI",
"license": "MIT",
"type": "module",

View File

@ -1,6 +1,6 @@
{
"name": "@aoagents/ao-plugin-agent-opencode",
"version": "0.2.0",
"version": "0.2.2",
"description": "Agent plugin: OpenCode",
"license": "MIT",
"type": "module",

View File

@ -1,6 +1,6 @@
{
"name": "@aoagents/ao-plugin-notifier-composio",
"version": "0.2.0",
"version": "0.2.2",
"description": "Notifier plugin: Composio unified notifications (Slack, Discord, email)",
"license": "MIT",
"type": "module",

View File

@ -1,6 +1,6 @@
{
"name": "@aoagents/ao-plugin-notifier-desktop",
"version": "0.2.0",
"version": "0.2.2",
"description": "Notifier plugin: OS desktop notifications",
"license": "MIT",
"type": "module",

View File

@ -1,6 +1,6 @@
{
"name": "@aoagents/ao-plugin-notifier-discord",
"version": "0.1.0",
"version": "0.1.2",
"description": "Notifier plugin: Discord webhook notifications",
"license": "MIT",
"type": "module",

View File

@ -1,6 +1,6 @@
{
"name": "@aoagents/ao-plugin-notifier-openclaw",
"version": "0.1.1",
"version": "0.1.3",
"description": "Notifier plugin: OpenClaw webhook notifications",
"license": "MIT",
"type": "module",

View File

@ -1,6 +1,6 @@
{
"name": "@aoagents/ao-plugin-notifier-slack",
"version": "0.2.0",
"version": "0.2.2",
"description": "Notifier plugin: Slack",
"license": "MIT",
"type": "module",

View File

@ -1,6 +1,6 @@
{
"name": "@aoagents/ao-plugin-notifier-webhook",
"version": "0.2.0",
"version": "0.2.2",
"description": "Notifier plugin: generic webhook",
"license": "MIT",
"type": "module",

View File

@ -1,6 +1,6 @@
{
"name": "@aoagents/ao-plugin-runtime-process",
"version": "0.2.0",
"version": "0.2.2",
"description": "Runtime plugin: child processes",
"license": "MIT",
"type": "module",

View File

@ -1,6 +1,6 @@
{
"name": "@aoagents/ao-plugin-runtime-tmux",
"version": "0.2.0",
"version": "0.2.2",
"description": "Runtime plugin: tmux sessions",
"license": "MIT",
"type": "module",

View File

@ -1,6 +1,6 @@
{
"name": "@aoagents/ao-plugin-scm-github",
"version": "0.2.0",
"version": "0.2.2",
"description": "SCM plugin: GitHub (PRs, CI, reviews)",
"license": "MIT",
"type": "module",

View File

@ -1,6 +1,6 @@
{
"name": "@aoagents/ao-plugin-scm-gitlab",
"version": "0.1.1",
"version": "0.1.3",
"description": "SCM plugin: GitLab (MRs, CI, reviews)",
"license": "MIT",
"type": "module",

View File

@ -1,6 +1,6 @@
{
"name": "@aoagents/ao-plugin-terminal-iterm2",
"version": "0.2.0",
"version": "0.2.2",
"description": "Terminal plugin: macOS iTerm2",
"license": "MIT",
"type": "module",

View File

@ -1,6 +1,6 @@
{
"name": "@aoagents/ao-plugin-terminal-web",
"version": "0.2.0",
"version": "0.2.2",
"description": "Terminal plugin: xterm.js web terminal",
"license": "MIT",
"type": "module",

View File

@ -1,6 +1,6 @@
{
"name": "@aoagents/ao-plugin-tracker-github",
"version": "0.2.0",
"version": "0.2.2",
"description": "Tracker plugin: GitHub Issues",
"license": "MIT",
"type": "module",

View File

@ -1,6 +1,6 @@
{
"name": "@aoagents/ao-plugin-tracker-gitlab",
"version": "0.1.1",
"version": "0.1.3",
"description": "Tracker plugin: GitLab Issues",
"license": "MIT",
"type": "module",

View File

@ -1,6 +1,6 @@
{
"name": "@aoagents/ao-plugin-tracker-linear",
"version": "0.2.0",
"version": "0.2.2",
"description": "Tracker plugin: Linear",
"license": "MIT",
"type": "module",

View File

@ -1,6 +1,6 @@
{
"name": "@aoagents/ao-plugin-workspace-clone",
"version": "0.2.0",
"version": "0.2.2",
"description": "Workspace plugin: git clone",
"license": "MIT",
"type": "module",

View File

@ -1,6 +1,6 @@
{
"name": "@aoagents/ao-plugin-workspace-worktree",
"version": "0.2.0",
"version": "0.2.2",
"description": "Workspace plugin: git worktrees",
"license": "MIT",
"type": "module",

View File

@ -1,6 +1,6 @@
{
"name": "@aoagents/ao-web",
"version": "0.2.2",
"version": "0.2.4",
"description": "Web dashboard for agent-orchestrator",
"type": "module",
"files": [