save

A command that saves a coding session's code and an AI-generated handoff summary to a Git repository, a system for tracking and sharing code changes.

In plain words
What is it for?
It creates or reads a handoff, adds it to .convosync/session-handoff.md, commits the changes, and pushes them to the remote repository. An optional commit message can be supplied.
Why use it?
It preserves both the code and the conversation context so work can continue on another device.

Command

Install

Getting it into your agent

One page per mod, every tool's command on it. A separate URL per tool would split the same page into five that compete with each other.

agentmods
npx agentmods add commands/convocli/convosync-plugin/save
Clone the repo
git clone --depth 1 https://github.com/convocli/convosync-plugin
Per session 0 Only the description is in the session, so the agent can decide to use it. The body loads when it is invoked.
When invoked 2,730 The whole file, excluding the scripts and references it only reads on demand.
Security scan A 1 finding. Scan, not verified.
Origin original No closer match found in the catalogue.
Token cost

What it costs to keep this loaded

Counted locally with the o200k_base tokenizer, which is exact for GPT models; Claude uses its own tokenizer and its counts differ. Treat this as one consistent yardstick across the catalogue rather than a bill. Prices are per million input tokens.

ModelPer sessionOnce invoked
Fable 5 $0.00000 $0.02730
Opus 5 $0.00000 $0.01365
Sonnet 5 $0.00000 $0.00546
Haiku 4.5 $0.00000 $0.00273

Measured yesterday against content hash 9227ff7eee2e, method: parsed. Prices are Anthropic first-party input rates as of 2026-08-30, from the pricing page.

Security

Grade A, and why

save scanned grade A with 1 finding against 26 rules in 11 categories — prompt injection, anti-refusal, data exfiltration, privilege escalation, supply chain, agent snooping, system-prompt leakage, SSRF and excessive agency — measured yesterday.

A static scan of the body, not an audit. Every finding is printed with the line that produced it so you can judge whether it matters here. A mod is markdown that instructs an agent; that is exactly why what it instructs is worth reading.

Runs shell commandslowCapability

Expected in a hook, worth knowing in a rule or an instructions file.

hostname = subprocess.run(['hostname'], capture_output=True, text=True).stdout.strip()
commands/save.md · 370 lines

How it starts

The opening of the file, as written. The whole thing — 370 lines — stays where its author put it; the contents beside it link to each section on GitHub.

Save ConvoSync Session

Save current coding session with AI-generated handoff for cross-device sync.

What this does:

  1. Checks if handoff draft exists - if not, automatically prompts Claude to generate one
  2. Reads the session handoff draft
  3. Appends it to .convosync/session-handoff.md with metadata
  4. Commits code changes and handoff to git
  5. Pushes to remote repository

Usage:

/convosync:save "your commit message"

Or omit the message to auto-generate from handoff:

/convosync:save

Notes:

  • Commit message is optional - If omitted, ConvoSync extracts the "Current Task" from the handoff to create a meaningful commit message
  • If no handoff draft exists, Claude will generate one automatically
  • You can also pre-generate a handoff using /convosync:generate-handoff to review it first
  • The handoff captures both code AND conversation context

Execute the save process:

python3 << 'SAVE_SCRIPT'
import subprocess
import sys
from pathlib import Path
from datetime import datetime
import re

# Get commit message from arguments (None if not provided - will auto-generate from handoff)
commit_msg = ' '.join(sys.argv[1:]) if len(sys.argv) > 1 else None

print("💾 ConvoSync: Saving session...")
print()

# ============================================================================
# 1. Get or create device ID
# ============================================================================

def get_or_create_device_id():
    """Get unique device identifier, create if doesn't exist."""
    device_file = Path('.convosync/device-id')

    if device_file.exists():
        return device_file.read_text().strip()

    # Generate device ID from hostname
    try:
        hostname = subprocess.run(['hostname'], capture_output=True, text=True).stdout.strip()
        # Sanitize hostname (remove special chars)
        device_id = re.sub(r'[^a-zA-Z0-9-]', '-', hostname)
    except:
        device_id = 'unknown-device'

    # Prompt if generic hostname
    if device_id.lower() in ['localhost', 'android', 'termux', 'pc', 'unknown-device', '']:
        print("┌────────────────────────────────────────────────────────────────┐")
        print("│ DEVICE IDENTIFICATION                                          │")
        print("└────────────────────────────────────────────────────────────────┘")
        print()
        print(f"Detected hostname: '{device_id}'")
        print()
        print("Please give this device a friendly name")
        print("(e.g., 'desktop', 'mobile', 'laptop', 'work-laptop'):")
        print()
        try:
            user_input = input("Device name: ").strip()
            if user_input:
                device_id = re.sub(r'[^a-zA-Z0-9-]', '-', user_input)
        except:
            pass  # Keep default if input fails
        print()

    # Save device ID
    device_file.parent.mkdir(exist_ok=True)
    device_file.write_text(device_id)
    subprocess.run(['git', 'add', str(device_file)], check=False)

    return device_id

device_id = get_or_create_device_id()
print(f"→ Device: {device_id}")
print()

# ============================================================================
# 2. Check for handoff draft
# ============================================================================

draft_file = Path('.convosync/session-handoff-draft.md')

if not draft_file.exists():
    print("┌────────────────────────────────────────────────────────────────┐")
    print("│ ⚠️  NO HANDOFF DRAFT FOUND - GENERATING NOW                    │")
    print("└────────────────────────────────────────────────────────────────┘")
    print()
    print("No handoff draft found. I'll generate one for you now.")
    print()
    print("=" * 70)
    print("SESSION HANDOFF GENERATION")
    print("=" * 70)
    print()
    print("Please generate a comprehensive session handoff with these sections:")
    print()
    print("### Current Task")
    print("Brief description of what we're currently working on.")
    print()
    print("### Progress So Far")
    print("List of accomplishments:")
    print("- ✅ Use checkmark for completed items")
    print("- ⏳ Use hourglass for in-progress items")
    print()
    print("### Key Decisions Made")
    print("Important technical or design decisions:")
    print("- What was decided")
    print("- Why it was decided that way")
    print()
    print("### Important Context")
    print("Non-code context to preserve:")
    print("- User preferences or facts mentioned")
    print("- Constraints or requirements")
    print("- Assumptions made")
    print()
    print("### Blockers/Waiting On")
    print("What's blocking progress:")
    print("- External dependencies or waiting on others")
    print("- If nothing blocking, write 'None - ready to proceed'")
    print()
    print("### Environment/Setup")
    print("Environment or setup changes:")
    print("- New dependencies installed")
    print("- Env vars added or changed")
    print("- Services that need to be running")
    print("- If no changes, write 'No environment changes'")
    print()
    print("### Known Issues/Debt")
    print("Technical debt or shortcuts:")
    print("- Shortcuts with file:line refs")
    print("- TODOs added to code")
    print("- Known bugs or workarounds")
    print("- If none, write 'No known issues'")
    print()
    print("### Debug/Error Context")
    print("If debugging:")
    print("- Error messages or stack traces")
    print("- Steps to reproduce")
    print("- What's been tried")
    print("- If not debugging, write 'No active debugging'")
    print()
    print("### Next Steps")
    print("What should be done next:")
    print("1. Specific next action")
    print("2. Another action")
    print()
    print("### Files Modified")
    print("List of changed files:")
    print("- filename.ts (+50, -10) - description of changes")
    print()
    print("### Open Questions")
    print("Unresolved questions or pending decisions")
    print()
    print("=" * 70)
    print()
    print("After generating the handoff above, save it to:")
    print("  .convosync/session-handoff-draft.md")
    print()
    print("Then re-run: /convosync:save \"your message\"")
    print()
    sys.exit(0)

handoff_content = draft_file.read_text().strip()
print("✓ Found handoff draft")
print()

# Auto-generate commit message from handoff if not provided
if commit_msg is None:
    try:
        # Extract Current Task section from handoff
        task_match = re.search(r'### Current Task\n(.+?)(?:\n\n|\n###|$)', handoff_content, re.DOTALL)
        if task_match:
            current_task = task_match.group(1).strip()
            # Truncate to 72 chars (git best practice for commit messages)
            if len(current_task) > 72:
                commit_msg = current_task[:69] + "..."
            else:
                commit_msg = current_task
            print(f"→ Auto-generated commit message from handoff")
        else:
            commit_msg = "WIP: sync session"
    except:
        commit_msg = "WIP: sync session"

print()

# ============================================================================
# 3. Get git info
# ============================================================================

try:
    commit_hash = subprocess.run(['git', 'rev-parse', 'HEAD'], capture_output=True, text=True, check=True).stdout.strip()
    branch = subprocess.run(['git', 'rev-parse', '--abbrev-ref', 'HEAD'], capture_output=True, text=True, check=True).stdout.strip()
except subprocess.CalledProcessError:
    print("❌ Not in a git repository")
    print("   ConvoSync requires the project to be in a git repository.")
    sys.exit(1)

timestamp = datetime.utcnow().isoformat() + 'Z'

# ============================================================================
# 4. Format handoff with metadata
# ============================================================================

formatted_handoff = f"""---
## Handoff from {device_id}
**Timestamp:** {timestamp}
**Commit:** {commit_hash}
**Branch:** {branch}

{handoff_content}

"""

# ============================================================================
# 5. Append to main handoff file
# ============================================================================

handoff_file = Path('.convosync/session-handoff.md')
handoff_file.parent.mkdir(exist_ok=True)

# Create file with header if it doesn't exist
if not handoff_file.exists():
    handoff_file.write_text("# ConvoSync Session Handoffs\n\n")

# Append new handoff
with open(handoff_file, 'a') as f:
    f.write(formatted_handoff)

print("✓ Handoff appended to .convosync/session-handoff.md")
print()

# ============================================================================
# 6. Git operations
# ============================================================================

print("→ Committing code and handoff...")
subprocess.run(['git', 'add', '.'], check=False)

try:
    result = subprocess.run(['git', 'commit', '-m', commit_msg], check=True, capture_output=True, text=True)
    # Print first line of commit output
    first_line = result.stdout.split('\n')[0] if result.stdout else ''
    if first_line:
        print(f"  {first_line}")
except subprocess.CalledProcessError as e:
    if 'nothing to commit' in e.stderr.lower():
        print("  No changes to commit")
    else:
        print(f"  Commit failed: {e.stderr}")

print()
print("→ Pushing to remote...")
try:
    subprocess.run(['git', 'push'], check=True, capture_output=True)
    print("  Pushed successfully")
except subprocess.CalledProcessError as e:
    print(f"  ⚠️  Push failed: {e.stderr}")
    print("  You may need to push manually later")

print()

commit_short = subprocess.run(['git', 'rev-parse', '--short', 'HEAD'], capture_output=True, text=True).stdout.strip()

print(f"✓ Code committed: {commit_short}")
print(f"✓ Handoff saved from device: {device_id}")
print()

# ============================================================================
# 7. Cleanup draft
# ============================================================================

try:
    draft_file.unlink()
    print("✓ Draft handoff cleaned up")
except:
    pass

print()
print("╔════════════════════════════════════════════════════════════════════╗")
print("║ ✅ SESSION SAVED SUCCESSFULLY!                                     ║")
print("╚════════════════════════════════════════════════════════════════════╝")
print()
print("To resume on another device:")
print("  1. cd to project directory")
print("  2. git pull")
print("  3. Run: /convosync:resume")
print()
print("The handoff from this device will be visible on other devices,")
print("providing full context of what you worked on.")

SAVE_SCRIPT

Read the full file on GitHub · 370 lines

Changes

What this file has done since we first saw it

Hashed on every crawl. A supply-chain change to an agent config is a question of when, not whether, so the history is kept rather than the latest state alone.

  1. yesterday First seen · 370 lines · 0 tokens per session scan A 9227ff7eee2e

Subscribe to this mod's changes

save is a command published in the GitHub repository convocli/convosync-plugin (4 stars, last pushed 10mo ago), licensed MIT. It costs nothing until one of its globs matches a file; then it loads 2,730 tokens. A static security scan graded it A with 1 finding (runs shell commands). No closer match exists in the catalogue, so it is treated as the original; first seen 2026-08-31.