{"record":{"id":"b336c79d5b066816","repo":"ruvnet/ruflo","slug":"no-active-session-state-found-at-claude-flow-sess","errorCode":null,"errorMessage":"No active session state found at .claude-flow/sessions/current.json","messagePattern":"No active session state found at \\.claude-flow/sessions/current\\.json","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"warning","filePath":"v3/@claude-flow/cli/src/mcp-tools/hooks-tools.ts","lineNumber":2374,"sourceCode":"\n// Session end hook - stops daemon\nexport const hooksSessionEnd: MCPTool = {\n  name: 'hooks_session-end',\n  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.',\n  inputSchema: {\n    type: 'object',\n    properties: {\n      saveState: { type: 'boolean', description: 'Save session state' },\n      exportMetrics: { type: 'boolean', description: 'Export session metrics' },\n      stopDaemon: { type: 'boolean', description: 'Stop worker daemon (default: true)' },\n    },\n  },\n  handler: async (params: Record<string, unknown>) => {\n    const saveState = params.saveState !== false;\n    const shouldStopDaemon = params.stopDaemon !== false;\n    const session = loadActiveSessionState();\n    if (!session) {\n      throw new Error('No active session state found at .claude-flow/sessions/current.json');\n    }\n    const sessionId = session.id;\n    const endedAt = Date.now();\n    const duration = Math.max(0, endedAt - Date.parse(session.startedAt));\n    const activity = loadSessionActivity(session, endedAt);\n    const summary = buildSessionSummary(activity, duration);\n\n    // Stop daemon if enabled\n    let daemonStopped = false;\n    if (shouldStopDaemon) {\n      try {\n        const { stopDaemon } = await import('../services/worker-daemon.js');\n        await stopDaemon();\n        daemonStopped = true;\n      } catch {\n        // Daemon may not be running\n      }\n    }","sourceCodeStart":2356,"sourceCodeEnd":2392,"githubUrl":"https://github.com/ruvnet/ruflo/blob/5234333c3462640ab348363ba4a142945fd2bc47/v3/@claude-flow/cli/src/mcp-tools/hooks-tools.ts#L2356-L2392","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["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.","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.","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.","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.","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."],"exampleFix":"// before: session_end invoked with no session started\nconst result = await mcp.callTool('session_end', { exportMetrics: true });\n// -> Error: No active session state found at .claude-flow/sessions/current.json\n\n// after: start the session first, then end it\nawait mcp.callTool('session_start', { sessionId: 'sess-123' });\n// ... work ...\nconst result = await mcp.callTool('session_end', { exportMetrics: true, saveState: true });","handlingStrategy":"try-catch","validationCode":"import { existsSync } from 'node:fs';\nimport { join } from 'node:path';\n\n// Run before calling session_end\nconst sessionFile = join(process.cwd(), '.claude-flow', 'sessions', 'current.json');\nif (!existsSync(sessionFile)) {\n  // No session to close: either start one or skip the end call\n  console.log('No active session; skipping session_end.');\n} else {\n  await mcp.callTool('session_end', { exportMetrics: true });\n}","typeGuard":"import { readFileSync } from 'node:fs';\n\ninterface ActiveSessionState { id: string; startedAt: string }\n\nfunction hasActiveSession(path: string): ActiveSessionState | null {\n  try {\n    const raw = JSON.parse(readFileSync(path, 'utf-8'));\n    if (typeof raw?.id === 'string' && typeof raw?.startedAt === 'string') return raw;\n  } catch { /* missing or unparsable */ }\n  return null;\n}","tryCatchPattern":"try {\n  await mcp.callTool('session_end', { exportMetrics: true });\n} catch (err) {\n  if (err instanceof Error && err.message.includes('No active session state found')) {\n    // Benign: nothing to close (already ended or never started). Log and continue.\n    console.warn('session_end skipped: no active session.');\n  } else {\n    throw err; // real failure (daemon stop, metrics export) — surface it\n  }\n}","preventionTips":["Always pair session_start with session_end in hook configurations; never register the end hook alone.","Pin the working directory: run claude-flow commands from the project root that owns .claude-flow/, especially in CI where steps may execute in different cwds.","Make session teardown idempotent in your wrapper: check for .claude-flow/sessions/current.json (or catch this specific message) before/instead of blindly calling session_end.","Avoid sharing one worktree between concurrent agents writing session state; give each run its own checkout so current.json is not clobbered.","Add .claude-flow/sessions/ to cleanup exclusion lists (git clean filters, Dockerignore) so state survives between start and end steps."],"tags":["claude-flow","session-state","mcp-tools","hooks","filesystem","missing-file"],"backgroundTag":"missing-session-state-file","analyzedSha":"5234333c3462640ab348363ba4a142945fd2bc47","analyzedAt":"2026-08-21T18:14:11.839Z","contentChangedAt":"2026-08-21T18:14:11.839Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}