feat: session recovery toolset (orphan detector + /recover)
Reconstructs session logs from Claude Code transcripts when a session crashes or is closed before /save. Two entry points: - /recover <uuid|latest> : manual, Claude-reviewed reconstruction - detect_orphaned_sessions.py : scheduled scan that auto-builds logs for substantive, unsaved, not-yet-recovered transcripts (banner-marked RECOVERED-UNVERIFIED), commits them, and posts a #bot-alerts FYI. recover_session.py is the shared engine: Python extracts the verbatim command/config/reference timeline; Ollama drafts prose-only narrative. Machine-local ledger (.claude/state/) prevents reprocessing. Reviewed: git add scoped to own files, ledger written only after successful push, per-uuid idempotency, --max cap for unattended runs. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
95
.claude/scripts/register-orphan-detector.ps1
Normal file
95
.claude/scripts/register-orphan-detector.ps1
Normal file
@@ -0,0 +1,95 @@
|
||||
# register-orphan-detector.ps1
|
||||
# Register the "ClaudeTools - Orphaned Session Detector" scheduled task on this
|
||||
# Windows machine. The task runs detect_orphaned_sessions.py, which scans the
|
||||
# per-machine Claude Code transcript directory for unsaved substantive sessions,
|
||||
# auto-builds banner-marked recovery logs, commits + pushes them, and posts an
|
||||
# FYI to #bot-alerts.
|
||||
#
|
||||
# Mirrors the GrepAI watcher registration pattern in .claude/OLLAMA.md.
|
||||
#
|
||||
# Triggers:
|
||||
# - AtLogOn (catch sessions lost since the last logon)
|
||||
# - Daily, repeating every 4 hours (catch crashes during a long workday;
|
||||
# 4h cadence pairs with the detector's 90-minute idle gate so an active
|
||||
# session is never grabbed mid-flight)
|
||||
#
|
||||
# Idempotent: -Force replaces any existing task with the same name.
|
||||
# This script only REGISTERS the task. It does not run the detector now.
|
||||
#
|
||||
# Run from an ordinary (non-admin) PowerShell:
|
||||
# powershell -ExecutionPolicy Bypass -File D:\claudetools\.claude\scripts\register-orphan-detector.ps1
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
$TaskName = "ClaudeTools - Orphaned Session Detector"
|
||||
|
||||
# Resolve the repo root portably. Prefer claudetools_root from identity.json
|
||||
# (per-machine, gitignored); fall back to two levels up from this script
|
||||
# (.claude/scripts/ -> repo root), resolved to a full path.
|
||||
$ScriptDir = $PSScriptRoot
|
||||
$FallbackRoot = (Resolve-Path (Join-Path $ScriptDir "..\..")).Path
|
||||
$IdentityPath = Join-Path $FallbackRoot ".claude\identity.json"
|
||||
$RepoRoot = $FallbackRoot
|
||||
if (Test-Path $IdentityPath) {
|
||||
try {
|
||||
$identity = Get-Content -Raw -Path $IdentityPath | ConvertFrom-Json
|
||||
if ($identity.claudetools_root -and (Test-Path $identity.claudetools_root)) {
|
||||
$RepoRoot = (Resolve-Path $identity.claudetools_root).Path
|
||||
}
|
||||
} catch {
|
||||
Write-Host "[WARNING] Could not parse $IdentityPath; using $FallbackRoot" -ForegroundColor Yellow
|
||||
}
|
||||
}
|
||||
$Script = Join-Path $RepoRoot ".claude\scripts\detect_orphaned_sessions.py"
|
||||
|
||||
if (-not (Test-Path $Script)) {
|
||||
Write-Host "[ERROR] Detector not found at $Script" -ForegroundColor Red
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Resolve the py launcher's full path (the action's Execute wants an absolute
|
||||
# path; "py" alone usually resolves but we pin it for reliability under the
|
||||
# Task Scheduler's environment).
|
||||
$PyCmd = Get-Command py -ErrorAction SilentlyContinue
|
||||
if ($null -ne $PyCmd) {
|
||||
$PyPath = $PyCmd.Source
|
||||
} else {
|
||||
$PyPath = "py" # fall back to PATH resolution at run time
|
||||
}
|
||||
|
||||
$Action = New-ScheduledTaskAction `
|
||||
-Execute $PyPath `
|
||||
-Argument "`"$Script`"" `
|
||||
-WorkingDirectory $RepoRoot
|
||||
|
||||
# Trigger 1: at logon for the current user.
|
||||
$TriggerLogon = New-ScheduledTaskTrigger -AtLogOn -User $env:USERNAME
|
||||
|
||||
# Trigger 2: daily at a fixed start, repeating every 4 hours all day.
|
||||
$TriggerDaily = New-ScheduledTaskTrigger -Daily -At 9am
|
||||
$TriggerDaily.Repetition = (New-ScheduledTaskTrigger `
|
||||
-Once -At 9am `
|
||||
-RepetitionInterval (New-TimeSpan -Hours 4) `
|
||||
-RepetitionDuration (New-TimeSpan -Hours 24)).Repetition
|
||||
|
||||
$Settings = New-ScheduledTaskSettingsSet `
|
||||
-ExecutionTimeLimit (New-TimeSpan -Minutes 30) `
|
||||
-MultipleInstances IgnoreNew `
|
||||
-StartWhenAvailable `
|
||||
-DontStopOnIdleEnd
|
||||
|
||||
Register-ScheduledTask `
|
||||
-TaskName $TaskName `
|
||||
-Action $Action `
|
||||
-Trigger $TriggerLogon, $TriggerDaily `
|
||||
-Settings $Settings `
|
||||
-Description "Scans Claude Code transcripts for unsaved substantive sessions and auto-recovers them into session logs." `
|
||||
-Force | Out-Null
|
||||
|
||||
Write-Host "[OK] Registered scheduled task '$TaskName'."
|
||||
Write-Host "[INFO] Action: $PyPath `"$Script`""
|
||||
Write-Host "[INFO] WorkDir: $RepoRoot"
|
||||
Write-Host "[INFO] Triggers: AtLogOn ($env:USERNAME) + daily every 4h"
|
||||
Write-Host "[INFO] To inspect: Get-ScheduledTask -TaskName '$TaskName' | Format-List"
|
||||
Write-Host "[INFO] To run now: Start-ScheduledTask -TaskName '$TaskName'"
|
||||
Write-Host "[INFO] To remove: Unregister-ScheduledTask -TaskName '$TaskName' -Confirm:`$false"
|
||||
Reference in New Issue
Block a user