Skill: kid0114/subsession-delete (version 1.0.0)

--- name: subsession-delete description: Delete an OpenClaw child session cleanly by removing its transcript, trajectory files, and sessions.json index entry. input: sessionKey or sessionId output: deleted files, updated index status, verification result --- # Subsession Delete Use this skill when you need to remove a spawned child session cleanly so it disappears from both disk and the session index/UI. ## Scope This skill removes a child session by: - deleting the main transcript `.jsonl` - deleting related `.trajectory.jsonl` and `.trajectory-path.json` - removing matching entries from `sessions.json` - verifying the session no longer appears in session listings ## Safety boundary - Treat this as destructive local deletion. - Confirm the exact target session before execution. - Prefer deleting child/subagent test sessions, not active primary sessions. - Default to dry-run first unless the user explicitly wants immediate deletion. ## Recommended command ```bash python3 skills/subsession-delete/scripts/delete_subsession.py \ --session-key 'agent:subtest2:subagent:EXAMPLE' \ --execute ``` You can also target by session id: ```bash python3 skills/subsession-delete/scripts/delete_subsession.py \ --session-id [REDACTED_SECRET] \ --agent-id subtest2 \ --execute ``` ## Behavior 1. Resolve the agent/session target from `sessionKey` or `sessionId` 2. Load the target agent's `sessions.json` 3. Identify all matching session files 4. Delete the files that exist 5. Remove matching `sessions.json` records 6. Verify the session id/key no longer appears in the index ## Report back Return: - target session key/id - deleted file paths - whether `sessions.json` changed - verification result ## Guardrails - Do not guess the target session. - Do not delete unrelated sessions in the same folder. - If the session is missing on disk but still indexed, clean the index too. - If the session is active/running, stop and ask before forcing deletion.

path: README.md
content: # subsession-delete

Delete a child session cleanly from disk and the session index.

Use the included script for dry-run or execute mode:

```bash
python3 skills/subsession-delete/scripts/delete_subsession.py --session-key 'agent:subtest2:subagent:EXAMPLE'
python3 skills/subsession-delete/scripts/delete_subsession.py --session-key 'agent:subtest2:subagent:EXAMPLE' --execute
```


path: scripts/delete_subsession.py
content: #!/usr/bin/env python3
from __future__ import annotations

import argparse
import json
import re
from pathlib import Path
from typing import Any, Optional

SESSION_KEY_RE = re.compile(r"^agent:(?P<agent>[^:]+):")


def parse_args() -> argparse.Namespace:
    p = argparse.ArgumentParser(description="Delete an OpenClaw child session cleanly.")
    p.add_argument("--session-key", help="Full session key, e.g. agent:subtest2:subagent:...")
    p.add_argument("--session-id", help="Session id UUID")
    p.add_argument("--agent-id", help="Required with --session-id when agent cannot be inferred")
    p.add_argument("--execute", action="store_true", help="Actually delete files and rewrite sessions.json")
    return p.parse_args()


def infer_agent_id(session_key: Optional[str], agent_id: Optional[str]) -> str:
    if agent_id:
        return agent_id
    if session_key:
        m = SESSION_KEY_RE.match(session_key)
        if m:
            return m.group("agent")
    raise SystemExit("Could not determine agent id; pass --agent-id.")


def load_json(path: Path) -> Any:
    return json.loads(path.read_text())


def find_session_id(data: Any, session_key: Optional[str], session_id: Optional[str]) -> str:
    if session_id:
        return session_id
    if not session_key:
        raise SystemExit("Provide --session-key or --session-id.")
    entry = data.get(session_key)
    if not isinstance(entry, dict) or not entry.get("sessionId"):
        raise SystemExit(f"Session key not found in sessions.json: {session_key}")
    return entry["sessionId"]


def prune(obj: Any, remove_id: str, remove_key: Optional[str]) -> Any:
    if isinstance(obj, dict):
        new = {}
        for k, v in obj.items():
            if remove_key and k == remove_key:
                continue
            if k == "sessionId" and v == remove_id:
                return None
            if k == "sessionFile" and isinstance(v, str) and remove_id in v:
                return None
            pruned = prune(v, remove_id, remove_key)
            if pruned is not None:
                new[k] = pruned
        return new
    if isinstance(obj, list):
        out = []
        for item in obj:
            pruned = prune(item, remove_id, remove_key)
            if pruned is not None:
                out.append(pruned)
        return out
    return obj


def main() -> None:
    args = parse_args()
    agent_id = infer_agent_id(args.session_key, args.agent_id)
    sessions_dir = Path.home() / ".openclaw" / "agents" / agent_id / "sessions"
    sessions_json = sessions_dir / "sessions.json"
    if not sessions_json.exists():
        raise SystemExit(f"sessions.json not found: {sessions_json}")

    data = load_json(sessions_json)
    target_session_id = find_session_id(data, args.session_key, args.session_id)

    file_candidates = [
        sessions_dir / f"{target_session_id}.jsonl",
        sessions_dir / f"{target_session_id}.trajectory.jsonl",
        sessions_dir / f"{target_session_id}.trajectory-path.json",
    ]

    existing = [str(p) for p in file_candidates if p.exists()]
    missing = [str(p) for p in file_candidates if not p.exists()]
    pruned = prune(data, target_session_id, args.session_key)
    index_changed = pruned != data

    result = {
        "agentId": agent_id,
        "sessionKey": args.session_key,
        "sessionId": target_session_id,
        "existingFiles": existing,
        "missingFiles": missing,
        "indexChanged": index_changed,
        "mode": "execute" if args.execute else "dry-run",
    }

    if args.execute:
        for path in file_candidates:
            if path.exists():
                path.unlink()
        if index_changed:
            sessions_json.write_text(json.dumps(pruned, ensure_ascii=False, indent=2) + "\n")
        result["deletedFiles"] = existing
        fresh = load_json(sessions_json)
        dumped = json.dumps(fresh, ensure_ascii=False)
        result["verified"] = target_session_id not in dumped and (args.session_key not in dumped if args.session_key else True)
    else:
        result["deletedFiles"] = []
        result["verified"] = False

    print(json.dumps(result, ensure_ascii=False, indent=2))


if __name__ == "__main__":
    main()


Security scan verdicts — ClawScan: clean | static analysis: clean | VirusTotal: clean | SkillSpector: suspicious (MEDIUM)