Claude Handoff

v1.0.0

Writes a structured handoff package when local agent determines cloud Claude Code is needed. This is the ONLY path from local pipeline to cloud — the agent n...

0· 0·0 current·0 all-time
byStephen Thorn@stephenlthorn
Security Scan
VirusTotalVirusTotal
Benign
View report →
OpenClawOpenClaw
Benign
high confidence
Purpose & Capability
Name/description claim: create a handoff package for manual Claude invocation. Implementation: writes .openclaw-skills/handoffs/*.md, constructs a ready-to-paste claude command, notifies the user, and logs locally. All required actions are coherent with the stated purpose and nothing in the code asks for unrelated cloud credentials or services.
Instruction Scope
SKILL.md and the Python code limit behavior to producing a handoff, writing project/home-scoped files, producing a shell command, and notifying the user. The skill does not auto-invoke Claude, does not read arbitrary system files, and does not collect unrelated environment data. The code expects context inputs (files_read, plan, errors) but does not itself escalate beyond the declared tooling (file-system, shell, notify).
Install Mechanism
Instruction-only skill with an included helper script; there is no install spec, no downloads, and no remote code execution during install. The runtime uses only stdlib Python modules (urllib optionally, sqlite3) and writes files locally.
Credentials
No required environment variables are declared, which matches the apparent lack of required credentials. The code will optionally POST notifications to an endpoint if OPENCLAW_NOTIFY_ENDPOINT is set — this env var is optional and not required by the skill, but if present the skill will attempt a network request with the notification payload. The skill also writes a persistent SQLite log in the user's home directory (~/.openclaw-skills/handoff-log.sqlite), which is reasonable for analytics but is persistent local storage.
Persistence & Privilege
always:false and user-invocable:true. The skill writes files under the project (.openclaw-skills/) and a local SQLite log under the user's home directory, which is appropriate for its function. It does not modify other skills or system-wide agent settings.
Assessment
This skill appears to do exactly what it says: create a handoff file and tell you how to run Claude yourself. Before installing or using it, review these points: (1) it will create files under your project in .openclaw-skills/handoffs/ and will write a small SQLite log at ~/.openclaw-skills/handoff-log.sqlite — if you care about disk hygiene or privacy, inspect or back up those locations; (2) if the OPENCLAW_NOTIFY_ENDPOINT environment variable is set, the skill will attempt to POST a small JSON notification (title/body/timestamp) to that endpoint — verify the endpoint's value if you do not want notifications sent; (3) the produced command gives Claude write access to your project when you run it manually — only run the produced claude command if you trust Claude Code for that task; (4) the skill does not auto-invoke remote models or require credentials, but review each generated handoff before sharing it because it may include absolute paths and project details.

Like a lobster shell, security has layers — review code before you run it.

latestvk977b688yvxe20jwzqf78yshfx858jsr
0downloads
0stars
1versions
Updated 3h ago
v1.0.0
MIT-0

Claude Code Handoff

The principle: never auto-invoke Claude

Claude Max quota is finite. Auto-invoking on every hard task burns it unnecessarily and removes user control. This skill ALWAYS produces a file and notifies the user — it never calls the Claude API directly.

The user reviews the handoff, decides if they want to spend quota on it, then runs Claude Code themselves. This is the contract.

Handoff package format

Written to {project_root}/.openclaw-skills/handoffs/{timestamp}-{slug}.md

---
timestamp: 2026-04-21T14:32:18Z
task_slug: add-swiftdata-cloudkit-migration
reason: iOS domain (hard gate)
orchestrator_version: 1.0.0
local_model: m27-jangtq-crack
---

# Handoff: Add SwiftData + CloudKit migration for iOS 26

## Original request
[verbatim user request]

## Why this is being handed off
**Reason**: iOS domain hard gate

Local M2.7 JANGTQ-CRACK's Swift training data is stale on SwiftData + CloudKit
migrations for iOS 26. This is a case where Claude Sonnet 4.6's more recent
training plus 1M context beats retrieved RAG snippets.

## Context gathered locally

### Files read
- `App/AppDelegate.swift` (112 lines)
- `Models/User.swift` (48 lines)
- `Models/UserDataSource.swift` (204 lines)
- `Views/ContentView.swift` (89 lines)

### Codebase patterns identified
- Project uses SwiftData with `@Model` macro
- Existing SwiftData stack in `Models/UserDataSource.swift:42`
- No CloudKit integration yet
- Deployment target: iOS 18, need to upgrade to iOS 26 first

### Relevant Apple documentation retrieved
1. [SwiftData CloudKit integration guide (iOS 26)](ref-link)
2. [iOS 26 migration patterns for SwiftData](ref-link)
3. [CloudKit schema synchronization](ref-link)

### Related Stack Overflow patterns
[if any]

## Proposed approach (from local planning)

Sub-problems identified:
1. **Update deployment target to iOS 26** (AppDelegate, Info.plist)
2. **Add CloudKit entitlement** (project settings)
3. **Migrate UserDataSource to use CloudKit-backed ModelContainer**
   - Depends on sub-problem 1
4. **Add migration plan for existing local data**
   - Depends on sub-problem 3
5. **Update Views to handle CloudKit sync state**
   - Depends on sub-problem 3

APIs involved:
- `ModelConfiguration(cloudKitDatabase:)` — iOS 26+
- `CKContainer.applicationDefault()` — iOS 8+
- `SchemaMigrationPlan` — iOS 26+

Risks:
- HIGH: Existing local user data could be lost if migration not handled
- MEDIUM: CloudKit development vs production environment confusion
- LOW: iOS version compatibility for existing users on < 26

## What local attempted and failed

[if build_failed]
Generated code that attempted the migration. Compile errors:
- Error 1: `cannot find 'cloudKitDatabase' in scope at ModelConfiguration.swift:12`
  (Fixed in iteration 2 — was missing import)
- Error 2: `'SchemaMigrationPlan' is only available in iOS 26.0 or newer`
  (Unable to fix — need guidance on availability strategy)

## Suggested Claude Code prompt

Copy-paste this into Claude Code:

Read /Users/stephen/projects/myapp/.openclaw-skills/handoffs/2026-04-21T14:32:18Z-add-swiftdata-cloudkit-migration.md

Then implement the 5 sub-problems in order. You have write access to the project. Run swift build after each sub-problem to verify.

When done, produce a summary of what changed and any remaining concerns.


Or run from terminal:
```bash
cd /Users/stephen/projects/myapp
claude "Read .openclaw-skills/handoffs/2026-04-21T14:32:18Z-add-swiftdata-cloudkit-migration.md and implement the plan"

Estimated Claude quota cost

Based on context size: ~12K input tokens + ~8K output = ~20K tokens. Sonnet 4.6: ~20K × $3/$15 per MTok = ~$0.15 in direct API equivalent. Via Max plan: draws modestly from your daily quota.


Generated by coding-orchestrator v1.0.0


## Execution

```python
import asyncio
import json
import re
from pathlib import Path
from datetime import datetime, timezone


HANDOFF_TEMPLATE = """---
timestamp: {timestamp}
task_slug: {slug}
reason: {reason_detail}
orchestrator_version: 1.0.0
local_model: m27-jangtq-crack
---

# Handoff: {task_title}

## Original request
{task}

## Why this is being handed off
**Reason**: {reason_detail}

{reason_explanation}

## Context gathered locally

### Files read
{files_read_section}

### Codebase patterns identified
{patterns_section}

### Relevant documentation retrieved
{docs_section}

## Proposed approach (from local planning)
{plan_section}

## What local attempted and failed
{attempt_section}

## Suggested Claude Code prompt

Copy-paste this into Claude Code:

Read {handoff_path}

Then implement the plan above. You have write access to the project. {execution_guidance}

When done, produce a summary of what changed and any remaining concerns.


Or run from terminal:
```bash
cd {project_root}
claude "Read {handoff_path_relative} and implement the plan"

Estimated Claude quota cost

Based on context size: ~{input_tokens}K input + ~{output_tokens}K output = ~{total_tokens}K tokens.


Generated by coding-orchestrator v1.0.0 """

REASON_EXPLANATIONS = { "hard_gate": """Local M2.7 JANGTQ-CRACK's Swift training data is 2+ years stale. iOS/Swift work consistently benefits from Sonnet 4.6's more recent training and deeper Apple framework knowledge.""",

"build_failed": """Local model generated code that failed to compile after 3

iterations. The remaining errors suggest the model is missing context that isn't in our RAG — likely an API interaction or constraint not present in training or retrieved docs.""",

"reflection_low_confidence": """After reflection passes, local model's confidence

in its own output is LOW. This usually means the model recognizes the code might work but isn't sure — a strong signal to get a second opinion.""",

"user_request": """User explicitly requested Claude Code handoff.""",

"context_overflow": """Task requires more context than M2.7's practical window can

accommodate. Sonnet 4.6's 1M context is the right tool.""",

"soft_gate": """Escalation score exceeded threshold based on task complexity,

file count, and uncertainty signals.""", }

async def claude_handoff( task: str, reason: str, context_gathered: dict = None, proposed_approach: dict = None, build_errors: list = None, critique_history: list = None, files_in_scope: list = None, project_root: str = None, ): timestamp = datetime.now(timezone.utc).isoformat() slug = _slugify(task)[:50] project_root = Path(project_root or ".")

# Build each section
sections = {
    "timestamp": timestamp,
    "slug": slug,
    "reason_detail": _reason_to_human(reason),
    "reason_explanation": REASON_EXPLANATIONS.get(reason, ""),
    "task": task,
    "task_title": _summarize_task(task),
    "files_read_section": _format_files(context_gathered),
    "patterns_section": _format_patterns(context_gathered),
    "docs_section": _format_docs(context_gathered),
    "plan_section": _format_plan(proposed_approach),
    "attempt_section": _format_attempt(build_errors, critique_history),
    "input_tokens": _estimate_input_tokens(context_gathered, proposed_approach) // 1000,
    "output_tokens": 8,
    "total_tokens": 20,
    "execution_guidance": _execution_guidance(proposed_approach, files_in_scope),
}

# Write handoff file
handoff_dir = project_root / ".openclaw-skills" / "handoffs"
handoff_dir.mkdir(parents=True, exist_ok=True)
handoff_path = handoff_dir / f"{timestamp.replace(':', '-')}-{slug}.md"

sections["handoff_path"] = str(handoff_path.absolute())
sections["handoff_path_relative"] = str(handoff_path.relative_to(project_root))
sections["project_root"] = str(project_root.absolute())

content = HANDOFF_TEMPLATE.format(**sections)
handoff_path.write_text(content)

# Build the paste-ready command
command = (
    f'cd {project_root.absolute()} && '
    f'claude "Read {sections["handoff_path_relative"]} and implement the plan"'
)

# Summary for user notification
summary = f"Handoff ready: {_summarize_task(task)} → Claude Code"

# Notify user via OpenClaw messaging channels
await notify_user(
    title="🤖 Claude Code handoff ready",
    body=f"{summary}\n\nPath: {sections['handoff_path_relative']}\n\n"
         f"Run: {command}",
    level="info"
)

return {
    "handoff_path": str(handoff_path.absolute()),
    "command": command,
    "summary": summary
}

def slugify(text): """Convert task text to filesystem-safe slug.""" slug = re.sub(r'[^\w\s-]', '', text.lower()) slug = re.sub(r'[\s-]+', '-', slug) return slug.strip('-')

def _reason_to_human(reason): mapping = { "hard_gate": "iOS domain (hard gate)", "build_failed": "Build errors unfixable locally", "reflection_low_confidence": "Low reflection confidence", "user_request": "User requested handoff", "context_overflow": "Task exceeds local context", "soft_gate": "Complexity threshold exceeded", } return mapping.get(reason, reason)

def _summarize_task(task): """Extract first sentence or 80 chars for title.""" first_sentence = task.split('.')[0].strip() if len(first_sentence) > 80: return first_sentence[:77] + "..." return first_sentence

def _format_files(ctx): if not ctx or "files_read" not in ctx: return "(none)" lines = [] for f in ctx["files_read"]: if isinstance(f, dict): lines.append(f"- {f['path']} ({f.get('lines', '?')} lines)") else: lines.append(f"- {f}") return "\n".join(lines)

def _format_patterns(ctx): if not ctx or "patterns" not in ctx: return "(none identified)" return "\n".join(f"- {p}" for p in ctx["patterns"])

def _format_docs(ctx): if not ctx or "retrieved_docs" not in ctx: return "(none retrieved)" return "\n".join( f"{i+1}. {d.get('title', d['url'])}" for i, d in enumerate(ctx["retrieved_docs"]) )

def _format_plan(plan): if not plan: return "(no plan developed — task escalated before planning)"

output = ["Sub-problems identified:"]
for i, sp in enumerate(plan.get("sub_problems", []), 1):
    output.append(f"{i}. **{sp['title']}**")
    output.append(f"   {sp.get('description', '')}")
    if sp.get("depends_on"):
        output.append(f"   Depends on: {', '.join(sp['depends_on'])}")
    output.append("")

if plan.get("apis"):
    output.append("\nAPIs involved:")
    for api in plan["apis"]:
        output.append(f"- `{api['name']}` — {api.get('version_requirement', 'any')}")

if plan.get("risks"):
    output.append("\nRisks:")
    for r in plan["risks"]:
        output.append(f"- {r.get('severity', 'MEDIUM').upper()}: {r['risk']}")
        if r.get("mitigation"):
            output.append(f"  Mitigation: {r['mitigation']}")

return "\n".join(output)

def _format_attempt(build_errors, critique_history): if not build_errors and not critique_history: return "(no prior attempts — escalated before code generation)"

output = []
if build_errors:
    output.append("Build errors remaining after fix attempts:")
    for err in build_errors[:10]:  # cap at 10
        output.append(f"- `{err['file']}:{err['line']}`: {err['message']}")
    if len(build_errors) > 10:
        output.append(f"- ... and {len(build_errors) - 10} more")

if critique_history:
    output.append("\nReflection pass findings:")
    for pass_result in critique_history:
        output.append(f"\n**Pass {pass_result['pass']}**:")
        output.append(pass_result['critique'][:500])

return "\n".join(output)

def _execution_guidance(plan, files): if not plan: return "Investigate the codebase, form a plan, then implement."

order = plan.get("order", [])
if order:
    return f"Implement the {len(order)} sub-problems in the listed order. "\
           f"Run tests after each sub-problem."
return "Implement the proposed approach."

def _estimate_input_tokens(ctx, plan): """Rough token estimate for quota budgeting.""" total = 1000 # base overhead if ctx: for f in ctx.get("files_read", []): total += f.get("lines", 100) * 8 # ~8 tokens per line for d in ctx.get("retrieved_docs", []): total += 500 if plan: total += 1000 return total

async def notify_user(title, body, level="info"): """Send notification via OpenClaw's messaging channels.""" # OpenClaw notifies via WhatsApp, Telegram, Discord based on user config await openclaw.notify(title=title, body=body, level=level) # Also write to a dashboard file for status checking status_file = Path(".openclaw-skills/last_notification.json") status_file.write_text(json.dumps({ "title": title, "body": body, "timestamp": datetime.now().isoformat() }))


## Integration with other skills

- Invoked by `coding-orchestrator` at multiple points (hard gate, after
  failed build, after failed reflection)
- Reads context from `task.scratchpad` populated by earlier steps
- Writes to project's `.openclaw-skills/handoffs/` directory
- Adds entry to global handoff log at `~/.openclaw-skills/handoff-log.sqlite`

## Handoff log for analytics

Every handoff gets logged to SQLite:

```sql
CREATE TABLE handoff_log (
    id INTEGER PRIMARY KEY,
    timestamp TEXT NOT NULL,
    task_slug TEXT NOT NULL,
    reason TEXT NOT NULL,
    project_root TEXT,
    estimated_tokens INTEGER,
    user_ran_claude BOOLEAN,  -- updated when user runs claude
    claude_succeeded BOOLEAN,  -- updated manually or via claude output log
    notes TEXT
);

Query this log periodically to understand your escalation patterns:

  • Which reasons are most common? (if "build_failed" dominates, improve your iOS system prompt or RAG)
  • How often do handoffs actually get run? (if low, your escalation threshold is too aggressive)
  • How often does Claude Code succeed? (if it fails too, the task genuinely needs architectural input from you)

What handoff does NOT do

  • Does not execute Claude Code
  • Does not read your Claude Max quota
  • Does not cache or share handoffs between users
  • Does not modify source code (only writes to .openclaw-skills/handoffs/)

These are all explicit user actions. The skill's job is to prepare the best possible brief and then step aside.

Comments

Loading comments...