# NousResearch/hermes-agent · OpenClaw migration: source directory archiving and Hermes rebranding

## Open now

- Nothing open on GitHub.

Two of the three members concern the migration process not auto-archiving the OpenClaw source directory, with one also asking for coexistence guidance. The third proposes rebranding OpenClaw references to Hermes during migration, which is related to migration but a separate change.

## TL;DR

- 3 member(s): 0 open issue(s), 1 closed issue(s), 0 open PR(s), 1 merged PR(s), 1 PR(s) closed without merging.
- A fix was merged to main in PR #8210. Verify it addresses your case.
- **Recommended:** read PR #8210 (and any competing PRs below) before filing. Confirm the merged change covers your symptom.

## Maintainer resolution verdict

- Internal-tracker verdict over this cluster's issue members: `resolved` · deciding PR(s): `pr-8210`. This is distinct from the fix status above. Weigh it yourself.

## How this cluster was grouped

- Link evidence (`STRUCTURAL_SPANNING`): every member is connected by structural evidence: shared changed files, or a closing/superseded reference.
- Pairwise link density `0.67`: the share of member pairs that carry a link, counting a structural link in full and a relevance-only link at 0.36. `1.00` means every pair is linked.

## Closed / duplicate issues

- #8188: Migration should not auto-archive source directory; needs coexistence guidance

## Prior & competing PRs

- **#8210: feat: rebrand OpenClaw references to Hermes during migration**. merged to main · fix tier `verified`
  - maintainer direction: category `merged`
  - files: `docs/migration/openclaw.md`, `hermes_cli/claw.py`, `optional-skills/migration/openclaw-migration/scripts/openclaw_to_hermes.py`, `tests/hermes_cli/test_claw.py`, `tests/skills/test_openclaw_migration.py`, `website/docs/guides/migrate-from-openclaw.md`, `website/docs/reference/cli-commands.md`
  - fix hunks:
  - `docs/migration/openclaw.md`

    ```diff
    @@ -118,7 +118,7 @@ For executed migrations, the full report is saved to `~/.hermes/migration/opencl
     ## Troubleshooting
     
     ### "OpenClaw directory not found"
    -The migration looks for `~/.openclaw` by default, then tries `~/.clawdbot` and `~/.moldbot`. If your OpenClaw is installed elsewhere, use `--source`:
    +The migration looks for `~/.openclaw` by default, then tries `~/.clawdbot` and `~/.moltbot`. If your OpenClaw is installed elsewhere, use `--source`:
     ```bash
     hermes claw migrate --source /path/to/.openclaw
     ```
    ```
  - `hermes_cli/claw.py`

    ```diff
    @@ -50,7 +50,7 @@
     )
     
     # Known OpenClaw directory names (current + legacy)
    -_OPENCLAW_DIR_NAMES = (".openclaw", ".clawdbot", ".moldbot")
    +_OPENCLAW_DIR_NAMES = (".openclaw", ".clawdbot", ".moltbot")
     
     def _warn_if_gateway_running(auto_yes: bool) -> None:
         """Check if a Hermes gateway is running with connected platforms.
    ```

    ```diff
    @@ -87,8 +87,8 @@ def _warn_if_gateway_running(auto_yes: bool) -> None:
             print_info("Migration cancelled. Stop the gateway and try again.")
             sys.exit(0)
     
    -# State files commonly found in OpenClaw workspace directories that cause
    -# confusion after migration (the agent discovers them and writes to them)
    +# State files commonly found in OpenClaw workspace directories — listed
    +# during cleanup to help the user decide whether to archive
     _WORKSPACE_STATE_GLOBS = (
         "*/todo.json",
         "*/sessions/*",
    ```

    ```diff
    @@ -133,7 +133,7 @@ def _find_openclaw_dirs() -> list[Path]:
     
     
     def _scan_workspace_state(source_dir: Path) -> list[tuple[Path, str]]:
    -    """Scan an OpenClaw directory for workspace state files that cause confusion.
    +    """Scan an OpenClaw directory for workspace state files.
     
         Returns a list of (path, description) tuples.
         """
    ```

    ```diff
    @@ -216,7 +216,7 @@ def _cmd_migrate(args):
             source_dir = Path.home() / ".openclaw"
             if not source_dir.is_dir():
                 # Try legacy directory names
    -            for legacy in (".clawdbot", ".moldbot"):
    +            for legacy in (".clawdbot", ".moltbot"):
                     candidate = Path.home() / legacy
                     if candidate.is_dir():
                         source_dir = candidate
    ```

    ```diff
    @@ -384,65 +384,16 @@ def _cmd_migrate(args):
         # Print results
         _print_migration_report(report, dry_run=False)
     
    -    # After successful migration, offer to archive the source directory
    -    if report.get("summary", {}).get("migrated", 0) > 0:
    -        _offer_source_archival(source_dir, auto_yes)
    -
    -
    -def _offer_source_archival(source_dir: Path, auto_yes: bool = False):
    -    """After migration, offer to rename the source directory to prevent state fragmentation.
    -
    -    OpenClaw workspace directories contain state files (todo.json, sessions, etc.)
    -    that the agent may discover and write to, causing confusion. Renaming the
    -    directory prevents this.
    -    """
    -    if not source_dir.is_dir():
    -        return
    -
    -    # Scan for state files that could cause problems
    -    state_files = _scan_workspace_state(source_dir)
    -
    -    print()
    -    print_header("Post-Migration Cleanup")
    -    print_info("The OpenClaw directory still exists and contains workspace state files")
    -    print_info("that can confuse the agent (todo lists, sessions, logs).")
    -    if state_files:
    -        print()
    -        print(color("  Found state files:", Colors.YELLOW))
    -        # Show up to 10 most relevant findings
    -        for path, desc in state_files[:10]:
    -            print(f"      {desc}")
    -        if len(state_files) > 10:
    -            print(f"      ... and {len(state_files) - 10} more")
    -    print()
    -    print_info(f"Recommend: rename {source_dir.name}/ to {source_dir.name}.pre-migration/")
    -    print_info("This prevents the agent from discovering old workspace directories.")
    -    print_info("You can always rename it back if needed.")
    -    print()
    -
    -    if not auto_yes and not sys.stdin.isatty():
    -        print_info("Non-interactive session — skipping archival.")
    -        print_info("Run later with: hermes claw cleanup")
    -        return
    -
    -    if auto_yes or prompt_yes_no(f"Archive {source_dir} now?", default=True):
    -        try:
    -            archive_path = _archive_directory(source_dir)
    -            print_success(f"Archived: {source_dir} → {archive_path}")
    -            print_info("The original directory has been renamed, not deleted.")
    -            print_info(f"To undo: mv {archive_path} {source_dir}")
    -        except OSError as e:
    -            print_error(f"Could not archive: {e}")
    -            print_info(f"You can do it manually: mv {source_dir} {source_dir}.pre-migration")
    -    else:
    -        print_info("Skipped. You can archive later with: hermes claw cleanup")
    +    # Source directory is left untouched — archiving is not the migration
    +    # tool's responsibility.  Users who want to clean up can run
    +    # 'hermes claw cleanup' separately.
     
     
     def _cmd_cleanup(args):
         """Archive leftover OpenClaw directories after migration.
     
         Scans for OpenClaw directories that still exist after migration and offers
    -    to rename them to .pre-migration to prevent state fragmentation.
    +    to rename them to .pre-migration to free disk space.
         """
         dry_run = getattr(args, "dry_run", False)
         auto_yes = getattr(args, "yes", False)
    ```

    ```diff
    @@ -517,7 +468,7 @@ def _cmd_cleanup(args):
     
             if state_files:
                 print()
    -            print(color(f"  {len(state_files)} state file(s) that could cause confusion:", Colors.YELLOW))
    +            print(color(f"  {len(state_files)} state file(s) found:", Colors.YELLOW))
                 for path, desc in state_files[:8]:
                     print(f"      {desc}")
                 if len(state_files) > 8:
    ```
  - `optional-skills/migration/openclaw-migration/scripts/openclaw_to_hermes.py`

    ```diff
    @@ -376,6 +376,24 @@ def backup_existing(path: Path, backup_root: Path) -> Optional[Path]:
         return dest
     
     
    +# ── Brand rewriting ─────────────────────────────────────────
    +# Replace OpenClaw brand names with Hermes in migrated text so that
    +# memory entries, user profiles, SOUL.md, and workspace instructions
    +# read as self-referential to the new agent identity.
    +_REBRAND_PATTERNS: List[Tuple[re.Pattern, str]] = [
    +    (re.compile(r'\bOpen[\s-]?Claw\b', re.IGNORECASE), 'Hermes'),
    +    (re.compile(r'\bClawdBot\b', re.IGNORECASE), 'Hermes'),
    +    (re.compile(r'\bMoltBot\b', re.IGNORECASE), 'Hermes'),
    +]
    +
    +
    +def rebrand_text(text: str) -> str:
    +    """Replace OpenClaw / ClawdBot / MoltBot brand names with Hermes."""
    +    for pattern, replacement in _REBRAND_PATTERNS:
    +        text = pattern.sub(replacement, text)
    +    return text
    +
    +
     def parse_existing_memory_entries(path: Path) -> List[str]:
         if not path.exists():
             return []
    ```

    ```diff
    @@ -782,12 +800,13 @@ def write_overflow_entries(self, kind: str, entries: Sequence[str]) -> Optional[
             path.write_text("\n".join(entries) + "\n", encoding="utf-8")
             return path
     
    -    def copy_file(self, source: Path, destination: Path, kind: str) -> None:
    +    def copy_file(self, source: Path, destination: Path, kind: str,
    +                  transform: Optional[Any] = None) -> None:
             if not source or not source.exists():
                 return
     
             if destination.exists():
    -            if sha256_file(source) == sha256_file(destination):
    +            if not transform and sha256_file(source) == sha256_file(destination):
                     self.record(kind, source, destination, "skipped", "Target already matches source")
                     return
                 if not self.overwrite:
    ```

    ```diff
    @@ -797,7 +816,13 @@ def copy_file(self, source: Path, destination: Path, kind: str) -> None:
             if self.execute:
                 backup_path = self.maybe_backup(destination)
                 ensure_parent(destination)
    -            shutil.copy2(source, destination)
    +            if transform:
    +                content = read_text(source)
    +                content = transform(content)
    +                destination.write_text(content, encoding="utf-8")
    +                shutil.copystat(source, destination)
    +            else:
    +                shutil.copy2(source, destination)
                 self.record(kind, source, destination, "migrated", backup=str(backup_path) if backup_path else None)
             else:
                 self.record(kind, source, destination, "migrated", "Would copy")
    ```

    ```diff
    @@ -807,7 +832,7 @@ def migrate_soul(self) -> None:
             if not source:
                 self.record("soul", None, self.target_root / "SOUL.md", "skipped", "No OpenClaw SOUL.md found")
                 return
    -        self.copy_file(source, self.target_root / "SOUL.md", kind="soul")
    +        self.copy_file(source, self.target_root / "SOUL.md", kind="soul", transform=rebrand_text)
     
         def migrate_workspace_agents(self) -> None:
             source = self.source_candidate(
    ```

    ```diff
    @@ -821,7 +846,7 @@ def migrate_workspace_agents(self) -> None:
                 self.record("workspace-agents", source, None, "skipped", "No workspace target was provided")
                 return
             destination = self.workspace_target / WORKSPACE_INSTRUCTIONS_FILENAME
    -        self.copy_file(source, destination, kind="workspace-agents")
    +        self.copy_file(source, destination, kind="workspace-agents", transform=rebrand_text)
     
         def migrate_memory(self, source: Optional[Path], destination: Path, limit: int, kind: str) -> None:
             if not source or not source.exists():
    ```

    ```diff
    @@ -832,6 +857,7 @@ def migrate_memory(self, source: Optional[Path], destination: Path, limit: int,
             if not incoming:
                 self.record(kind, source, destination, "skipped", "No importable entries found")
                 return
    +        incoming = [rebrand_text(entry) for entry in incoming]
     
             existing = parse_existing_memory_entries(destination)
             merged, stats, overflowed = merge_entries(existing, incoming, limit)
    ```

    ```diff
    @@ -927,7 +953,7 @@ def migrate_command_allowlist(self) -> None:
     
         def load_openclaw_config(self) -> Dict[str, Any]:
             # Check current name and legacy config filenames
    -        for name in ("openclaw.json", "clawdbot.json", "moldbot.json"):
    +        for name in ("openclaw.json", "clawdbot.json", "moltbot.json"):
                 config_path = self.source_root / name
                 if config_path.exists():
                     try:
    ```

    ```diff
    @@ -997,7 +1023,17 @@ def migrate_messaging_settings(self, config: Optional[Dict[str, Any]] = None) ->
                 .get("workspace")
             )
             if isinstance(workspace, str) and workspace.strip():
    -            additions["MESSAGING_CWD"] = workspace.strip()
    +            ws_path = workspace.strip()
    +            # Skip if the workspace points inside the OpenClaw source directory —
    +            # that path will be stale after migration and would cause the Hermes
    +            # gateway to use the old OpenClaw workspace as its cwd, picking up
    +            # OpenClaw's AGENTS.md, MEMORY.md, etc.
    +            try:
    +                inside_source = Path(ws_path).resolve().is_relative_to(self.source_root.resolve())
    +            except (ValueError, OSError):
    +                inside_source = False
    +            if not inside_source:
    +                additions["MESSAGING_CWD"] = ws_path
     
             allowlist_path = self.source_root / "credentials" / "telegram-default-allowFrom.json"
             if allowlist_path.exists():
    ```

    ```diff
    @@ -1543,6 +1579,7 @@ def migrate_daily_memory(self) -> None:
             if not all_incoming:
                 self.record("daily-memory", source_dir, destination, "skipped", "No importable entries found in daily memory files")
                 return
    +        all_incoming = [rebrand_text(entry) for entry in all_incoming]
     
             existing = parse_existing_memory_entries(destination)
             merged, stats, overflowed = merge_entries(existing, all_incoming, self.memory_limit)
    ```
  - `tests/hermes_cli/test_claw.py`

    ```diff
    @@ -58,13 +58,13 @@ def test_finds_openclaw_dir(self, tmp_path):
         def test_finds_legacy_dirs(self, tmp_path):
             clawdbot = tmp_path / ".clawdbot"
             clawdbot.mkdir()
    -        moldbot = tmp_path / ".moldbot"
    -        moldbot.mkdir()
    +        moltbot = tmp_path / ".moltbot"
    +        moltbot.mkdir()
             with patch("pathlib.Path.home", return_value=tmp_path):
                 found = claw_mod._find_openclaw_dirs()
             assert len(found) == 2
             assert clawdbot in found
    -        assert moldbot in found
    +        assert moltbot in found
     
         def test_returns_empty_when_none_exist(self, tmp_path):
             with patch("pathlib.Path.home", return_value=tmp_path):
    ```

    ```diff
    @@ -297,7 +297,6 @@ def test_execute_with_confirmation(self, tmp_path, capsys):
                 patch.object(claw_mod, "_load_migration_module", return_value=fake_mod),
                 patch.object(claw_mod, "get_config_path", return_value=config_path),
                 patch.object(claw_mod, "prompt_yes_no", return_value=True),
    -            patch.object(claw_mod, "_offer_source_archival"),
                 patch("sys.stdin", mock_stdin),
             ):
                 claw_mod._cmd_migrate(args)
    ```

    ```diff
    @@ -306,43 +305,8 @@ def test_execute_with_confirmation(self, tmp_path, capsys):
             assert "Migration Results" in captured.out
             assert "Migration complete!" in captured.out
     
    -    def test_execute_offers_archival_on_success(self, tmp_path, capsys):
    -        """After successful migration, _offer_source_archival should be called."""
    -        openclaw_dir = tmp_path / ".openclaw"
    -        openclaw_dir.mkdir()
    -
    -        fake_mod = ModuleType("openclaw_to_hermes")
    -        fake_mod.resolve_selected_options = MagicMock(return_value={"soul"})
    -        fake_migrator = MagicMock()
    -        fake_migrator.migrate.return_value = {
    -            "summary": {"migrated": 3, "skipped": 0, "conflict": 0, "error": 0},
    -            "items": [
    -                {"kind": "soul", "status": "migrated", "destination": str(tmp_path / "SOUL.md")},
    -            ],
    -        }
    -        fake_mod.Migrator = MagicMock(return_value=fake_migrator)
    -
    -        args = Namespace(
    -            source=str(openclaw_dir),
    -            dry_run=False, preset="full", overwrite=False,
    -            migrate_secrets=[redacted:secret-assignment] workspace_target=None,
    -            skill_conflict="skip", yes=True,
    -        )
    -
    -        with (
    -            patch.object(claw_mod, "_find_migration_script", return_value=tmp_path / "s.py"),
    -            patch.object(claw_mod, "_load_migration_module", return_value=fake_mod),
    -            patch.object(claw_mod, "get_config_path", return_value=tmp_path / "config.yaml"),
    -            patch.object(claw_mod, "save_config"),
    -            patch.object(claw_mod, "load_config", return_value={}),
    -            patch.object(claw_mod, "_offer_source_archival") as mock_archival,
    -        ):
    -            claw_mod._cmd_migrate(args)
    -
    -        mock_archival.assert_called_once_with(openclaw_dir, True)
    -
    -    def test_dry_run_skips_archival(self, tmp_path, capsys):
    -        """Dry run should not offer archival."""
    +    def test_dry_run_does_not_touch_source(self, tmp_path, capsys):
    +        """Dry run should not modify the source directory."""
             openclaw_dir = tmp_path / ".openclaw"
             openclaw_dir.mkdir()
     
    ```

    ```diff
    @@ -369,11 +333,10 @@ def test_dry_run_skips_archival(self, tmp_path, capsys):
                 patch.object(claw_mod, "get_config_path", return_value=tmp_path / "config.yaml"),
                 patch.object(claw_mod, "save_config"),
                 patch.object(claw_mod, "load_config", return_value={}),
    -            patch.object(claw_mod, "_offer_source_archival") as mock_archival,
             ):
                 claw_mod._cmd_migrate(args)
     
    -        mock_archival.assert_not_called()
    +        assert openclaw_dir.is_dir()  # Source untouched
     
         def test_execute_cancelled_by_user(self, tmp_path, capsys):
             openclaw_dir = tmp_path / ".openclaw"
    ```

    ```diff
    @@ -506,73 +469,6 @@ def test_full_preset_enables_secrets(self, tmp_path, capsys):
             assert call_kwargs["migrate_secrets"] is True
     
     
    -# ---------------------------------------------------------------------------
    -# _offer_source_archival
    -# ---------------------------------------------------------------------------
    -
    -
    -class TestOfferSourceArchival:
    -    """Test the post-migration archival offer."""
    -
    -    def test_archives_with_auto_yes(self, tmp_path, capsys):
    -        source = tmp_path / ".openclaw"
    -        source.mkdir()
    -        (source / "workspace").mkdir()
    -        (source / "workspace" / "todo.json").write_text("{}")
    -
    -        claw_mod._offer_source_archival(source, auto_yes=True)
    -
    -        captured = capsys.readouterr()
    -        assert "Archived" in captured.out
    -        assert not source.exists()
    -        assert (tmp_path / ".openclaw.pre-migration").is_dir()
    -
    -    def test_skips_when_user_declines(self, tmp_path, capsys):
    -        source = tmp_path / ".openclaw"
    -        source.mkdir()
    -
    -        mock_stdin = MagicMock()
    -        mock_stdin.isatty.return_value = True
    -
    -        with (
    -            patch.object(claw_mod, "prompt_yes_no", return_value=False),
    -            patch("sys.stdin", mock_stdin),
    -        ):
    -            claw_mod._offer_source_archival(source, auto_yes=False)
    -
    -        captured = capsys.readouterr()
    -        assert "Skipped" in captured.out
    -        assert source.is_dir()  # Still exists
    -
    -    def test_noop_when_source_missing(self, tmp_path, capsys):
    -        claw_mod._offer_source_archival(tmp_path / "nonexistent", auto_yes=True)
    -        captured = capsys.readouterr()
    -        assert captured.out == ""  # No output
    -
    -    def test_shows_state_files(self, tmp_path, capsys):
    -        source = tmp_path / ".openclaw"
    -        source.mkdir()
    -        ws = source / "workspace"
    -        ws.mkdir()
    -        (ws / "todo.json").write_text("{}")
    -
    -        with patch.object(claw_mod, "prompt_yes_no", return_value=False):
    -            claw_mod._offer_source_archival(source, auto_yes=False)
    -
    -        captured = capsys.readouterr()
    -        assert "todo.json" in captured.out
    -
    -    def test_handles_archive_error(self, tmp_path, capsys):
    -        source = tmp_path / ".openclaw"
    -        source.mkdir()
    -
    -        with patch.object(claw_mod, "_archive_directory", side_effect=OSError("permission denied")):
    -            claw_mod._offer_source_archival(source, auto_yes=True)
    -
    -        captured = capsys.readouterr()
    -        assert "Could not archive" in captured.out
    -
    -
     # ---------------------------------------------------------------------------
     # _cmd_cleanup
     # ---------------------------------------------------------------------------
    ```
  - `tests/skills/test_openclaw_migration.py`

    ```diff
    @@ -185,6 +185,38 @@ def test_migrator_optionally_imports_supported_secrets_and_messaging_settings(tm
         assert "TELEGRAM_BOT_TOKEN=[redacted:secret-assignment]" in env_text
     
     
    +def test_messaging_cwd_skipped_when_inside_source(tmp_path: Path):
    +    """MESSAGING_CWD pointing inside the OpenClaw source dir should be skipped."""
    +    mod = load_module()
    +    source = tmp_path / ".openclaw"
    +    target = tmp_path / ".hermes"
    +    target.mkdir()
    +
    +    # Workspace path is inside the source directory
    +    ws_path = str(source / "workspace")
    +    (source / "credentials").mkdir(parents=True)
    +    (source / "openclaw.json").write_text(
    +        json.dumps({"agents": {"defaults": {"workspace": ws_path}}}),
    +        encoding="utf-8",
    +    )
    +
    +    migrator = mod.Migrator(
    +        source_root=source,
    +        target_root=target,
    +        execute=True,
    +        workspace_target=None,
    +        overwrite=False,
    +        migrate_secrets=True,
    +        output_dir=target / "migration-report",
    +        selected_options={"messaging-settings"},
    +    )
    +    migrator.migrate()
    +
    +    env_path = target / ".env"
    +    if env_path.exists():
    +        assert "MESSAGING_CWD" not in env_path.read_text(encoding="utf-8")
    +
    +
     def test_migrator_can_execute_only_selected_categories(tmp_path: Path):
         mod = load_module()
         source = tmp_path / ".openclaw"
    ```

    ```diff
    @@ -722,3 +754,98 @@ def test_skill_installs_cleanly_under_skills_guard():
         KNOWN_FALSE_POSITIVES = {"agent_config_mod", "python_os_environ", "hermes_config_mod"}
         for f in result.findings:
             assert f.pattern_id in KNOWN_FALSE_POSITIVES, f"Unexpected finding: {f}"
    +
    +
    +# ── rebrand_text tests ────────────────────────────────────────
    +
    +
    +def test_rebrand_text_replaces_openclaw_variants():
    +    mod = load_module()
    +    assert mod.rebrand_text("OpenClaw prefers Python 3.11") == "Hermes prefers Python 3.11"
    +    assert mod.rebrand_text("I told Open Claw to use dark mode") == "I told Hermes to use dark mode"
    +    assert mod.rebrand_text("Open-Claw config is great") == "Hermes config is great"
    +    assert mod.rebrand_text("openclaw should always respond concisely") == "Hermes should always respond concisely"
    +    assert mod.rebrand_text("OPENCLAW uses tools well") == "Hermes uses tools well"
    +
    +
    +def test_rebrand_text_replaces_legacy_bot_names():
    +    mod = load_module()
    +    assert mod.rebrand_text("ClawdBot remembers my timezone") == "Hermes remembers my timezone"
    +    assert mod.rebrand_text("clawdbot prefers tabs") == "Hermes prefers tabs"
    +    assert mod.rebrand_text("MoltBot was configured for Spanish") == "Hermes was configured for Spanish"
    +    assert mod.rebrand_text("moltbot uses Python") == "Hermes uses Python"
    +
    +
    +def test_rebrand_text_preserves_unrelated_content():
    +    mod = load_module()
    +    text = "User prefers dark mode and lives in Las Vegas"
    +    assert mod.rebrand_text(text) == text
    +
    +
    +def test_rebrand_text_handles_multiple_replacements():
    +    mod = load_module()
    +    text = "OpenClaw said to ask ClawdBot about MoltBot settings"
    +    assert mod.rebrand_text(text) == "Hermes said to ask Hermes about Hermes settings"
    +
    +
    +def test_migrate_memory_rebrands_entries(tmp_path):
    +    mod = load_module()
    +    source_root = tmp_path / "openclaw"
    +    source_root.mkdir()
    +    workspace = source_root / "workspace"
    +    workspace.mkdir()
    +    memory_md = workspace / "MEMORY.md"
    +    memory_md.write_text(
    +        "# Memory\n\n- OpenClaw should use Python 3.11\n- ClawdBot prefers dark mode\n",
    +        encoding="utf-8",
    +    )
    +
    +    target_root = tmp_path / "hermes"
    +    target_root.mkdir()
    +    (target_root / "memories").mkdir()
    +
    +    migrator = mod.Migrator(
    +        source_root=source_root,
    +        target_root=target_root,
    +        execute=True,
    +        workspace_target=None,
    +        overwrite=False,
    +        migrate_secrets=[redacted:secret-assignment]
    +        output_dir=tmp_path / "report",
    +        selected_options={"memory"},
    +    )
    +    migrator.migrate()
    +
    +    result = (target_root / "memories" / "MEMORY.md").read_text(encoding="utf-8")
    +    assert "OpenClaw" not in result
    +    assert "ClawdBot" not in result
    +    assert "Hermes" in result
    +
    +
    +def test_migrate_soul_rebrands_content(tmp_path):
    +    mod = load_module()
    +    source_root = tmp_path / "openclaw"
    +    source_root.mkdir()
    +    workspace = source_root / "workspace"
    +    workspace.mkdir()
    +    soul_md = workspace / "SOUL.md"
    +    soul_md.write_text("You are OpenClaw, an AI assistant made by SparkLab.", encoding="utf-8")
    +
    +    target_root = tmp_path / "hermes"
    +    target_root.mkdir()
    +
    +    migrator = mod.Migrator(
    +        source_root=source_root,
    +        target_root=target_root,
    +        execute=True,
    +        workspace_target=None,
    +        overwrite=False,
    +        migrate_secrets=[redacted:secret-assignment]
    +        output_dir=tmp_path / "report",
    +        selected_options={"soul"},
    +    )
    +    migrator.migrate()
    +
    +    result = (target_root / "SOUL.md").read_text(encoding="utf-8")
    +    assert "OpenClaw" not in result
    +    assert "You are Hermes" in result
    ```
  - `website/docs/guides/migrate-from-openclaw.md`

    ```diff
    @@ -23,7 +23,7 @@ hermes claw migrate --preset full --yes
     
     The migration always shows a full preview of what will be imported before making any changes. Review the list, then confirm to proceed.
     
    -Reads from `~/.openclaw/` by default. Legacy `~/.clawdbot/` or `~/.moldbot/` directories are detected automatically. Same for legacy config filenames (`clawdbot.json`, `moldbot.json`).
    +Reads from `~/.openclaw/` by default. Legacy `~/.clawdbot/` or `~/.moltbot/` directories are detected automatically. Same for legacy config filenames (`clawdbot.json`, `moltbot.json`).
     
     ## Options
     
    ```

    ```diff
    @@ -234,7 +234,7 @@ The migration resolves all three formats. For env templates and SecretRef object
     
     ### "OpenClaw directory not found"
     
    -The migration checks `~/.openclaw/`, then `~/.clawdbot/`, then `~/.moldbot/`. If your installation is elsewhere, use `--source /path/to/your/openclaw`.
    +The migration checks `~/.openclaw/`, then `~/.clawdbot/`, then `~/.moltbot/`. If your installation is elsewhere, use `--source /path/to/your/openclaw`.
     
     ### "No provider API keys found"
     
    ```
  - `website/docs/reference/cli-commands.md`

    ```diff
    @@ -660,7 +660,7 @@ hermes insights [--days N] [--source platform]
     hermes claw migrate [options]
     ```
     
    -Migrate your OpenClaw setup to Hermes. Reads from `~/.openclaw` (or a custom path) and writes to `~/.hermes`. Automatically detects legacy directory names (`~/.clawdbot`, `~/.moldbot`) and config filenames (`clawdbot.json`, `moldbot.json`).
    +Migrate your OpenClaw setup to Hermes. Reads from `~/.openclaw` (or a custom path) and writes to `~/.hermes`. Automatically detects legacy directory names (`~/.clawdbot`, `~/.moltbot`) and config filenames (`clawdbot.json`, `moltbot.json`).
     
     | Option | Description |
     |--------|-------------|
    ```
- **#8192: fix(migration): don't auto-archive OpenClaw source directory**. closed without merge
  - maintainer direction: category `merged_via`; implication `superseded`; refs `#8210`

## Fix status

- Merged to main in PR #8210 (cited above). No release data is tracked. Verify downstream.
- Fix last verified (merged) `2026-04-12`. This confirmation is old and may be out of date (a revert/reopen would not have invalidated it here); re-verify.
