ruvnet/ruflo · warning · Error

No active session state found at .claude-flow/sessions/curre

Error message

No active session state found at .claude-flow/sessions/current.json

What it means

The session_end MCP tool in @claude-flow/cli requires an active session to close out. It calls loadActiveSessionState(), which reads .claude-flow/sessions/current.json under the project working directory (v3/@claude-flow/cli/src/mcp-tools/hooks-tools.ts:552-554); if that file does not exist or cannot be parsed, the function returns null and the handler at line 2374 throws this error. The file is normally created by the session-start flow (hooks session_start / pre-task), so hitting it means no session was ever opened, or the state file was deleted, or the tool ran from a different working directory.

Source

Thrown at v3/@claude-flow/cli/src/mcp-tools/hooks-tools.ts:2374

// Session end hook - stops daemon
export const hooksSessionEnd: MCPTool = {
  name: 'hooks_session-end',
  description: 'End current session, stop daemon, and persist state Use when native Bash hooks (via Claude Code\'s settings.json) are wrong because you need Ruflo-side state — pattern persistence, neural training signals, model-routing learning, cost tracking, audit chain. For one-off shell commands, plain Bash hooks are fine.',
  inputSchema: {
    type: 'object',
    properties: {
      saveState: { type: 'boolean', description: 'Save session state' },
      exportMetrics: { type: 'boolean', description: 'Export session metrics' },
      stopDaemon: { type: 'boolean', description: 'Stop worker daemon (default: true)' },
    },
  },
  handler: async (params: Record<string, unknown>) => {
    const saveState = params.saveState !== false;
    const shouldStopDaemon = params.stopDaemon !== false;
    const session = loadActiveSessionState();
    if (!session) {
      throw new Error('No active session state found at .claude-flow/sessions/current.json');
    }
    const sessionId = session.id;
    const endedAt = Date.now();
    const duration = Math.max(0, endedAt - Date.parse(session.startedAt));
    const activity = loadSessionActivity(session, endedAt);
    const summary = buildSessionSummary(activity, duration);

    // Stop daemon if enabled
    let daemonStopped = false;
    if (shouldStopDaemon) {
      try {
        const { stopDaemon } = await import('../services/worker-daemon.js');
        await stopDaemon();
        daemonStopped = true;
      } catch {
        // Daemon may not be running
      }
    }

View on GitHub (pinned to 5234333c34)

Solutions

  1. Verify .claude-flow/sessions/current.json exists in the project root you are running from (`ls .claude-flow/sessions/`); if missing, run the session-start flow first (`npx claude-flow hooks session-start --session-id <id>` or the session_start MCP tool), then retry session-end.
  2. Confirm the working directory matches the one where the session started: getProjectCwd() resolves the path relatively, so re-run from the original project root (or set CLAUDE_FLOW_CONFIG / project cwd appropriately) so .claude-flow/sessions/current.json is visible.
  3. If the file was consumed by an earlier session-end or deleted by cleanup, simply treat the session as already closed: re-run session-start to open a new session instead of retrying session-end.
  4. If the file exists but is corrupt/empty (0 bytes, partial JSON), delete it and start a fresh session; inspect for concurrent writers (two agents sharing one worktree) that truncated it.
  5. Audit your hooks configuration (e.g. .claude/settings.json hooks or codex config) to ensure SessionStart and SessionEnd hooks are registered as a pair, so end is never invoked without a preceding successful start.

Example fix

// before: session_end invoked with no session started
const result = await mcp.callTool('session_end', { exportMetrics: true });
// -> Error: No active session state found at .claude-flow/sessions/current.json

// after: start the session first, then end it
await mcp.callTool('session_start', { sessionId: 'sess-123' });
// ... work ...
const result = await mcp.callTool('session_end', { exportMetrics: true, saveState: true });
Defensive patterns

Strategy: try-catch

Validate before calling

import { existsSync } from 'node:fs';
import { join } from 'node:path';

// Run before calling session_end
const sessionFile = join(process.cwd(), '.claude-flow', 'sessions', 'current.json');
if (!existsSync(sessionFile)) {
  // No session to close: either start one or skip the end call
  console.log('No active session; skipping session_end.');
} else {
  await mcp.callTool('session_end', { exportMetrics: true });
}

Type guard

import { readFileSync } from 'node:fs';

interface ActiveSessionState { id: string; startedAt: string }

function hasActiveSession(path: string): ActiveSessionState | null {
  try {
    const raw = JSON.parse(readFileSync(path, 'utf-8'));
    if (typeof raw?.id === 'string' && typeof raw?.startedAt === 'string') return raw;
  } catch { /* missing or unparsable */ }
  return null;
}

Try / catch

try {
  await mcp.callTool('session_end', { exportMetrics: true });
} catch (err) {
  if (err instanceof Error && err.message.includes('No active session state found')) {
    // Benign: nothing to close (already ended or never started). Log and continue.
    console.warn('session_end skipped: no active session.');
  } else {
    throw err; // real failure (daemon stop, metrics export) — surface it
  }
}

Prevention

When it happens

Trigger: Calling the claude-flow session_end MCP tool (or `npx claude-flow hooks session-end`) without a prior successful session_start in the same project; running the tool from a cwd other than the one where the session started (getProjectCwd() resolves .claude-flow/sessions/current.json relative to project cwd); the state file having been removed by a cleanup step, git clean, or a previous session-end that already consumed it; a corrupted/unparsable current.json that makes loadActiveSessionState return null.

Common situations: Hook misconfiguration where only the SessionEnd hook is wired into the host (Claude Code / Codex) but SessionStart is not; running session-end twice in a row (the first run finalizes and the state is gone); switching between repo checkouts or worktrees so the relative .claude-flow path no longer contains the session; CI or container runs where the .claude-flow directory is not persisted between the start and end steps; version upgrades that changed the session-state location or schema, leaving an old setup writing to a path the new reader does not find.

Related errors


AI-assisted analysis of ruvnet/ruflo@5234333c34 (2026-08-21). Data as JSON: /api/errors/b336c79d5b066816. Report an issue: GitHub.