Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ All notable changes to OCM are documented here.
- Remove the hidden staging file and SQLite snapshot copies when `ocm env export` is stopped by SIGINT, SIGTERM or SIGHUP, and exit with status 130. Signals ignored at startup, as under `nohup`, stay ignored.
- Keep the Gateway running when `ocm service restart` is run again within seconds of a previous restart. The daemon no longer counts a restart handoff that `ocm service restart` requested toward its clean-exit restart loop guard, which left the Gateway stopped while the command waited 30 seconds and exited 0. A Gateway that keeps exiting with a handoff on its own is still stopped.
- Keep `~/...` config paths outside `~/.openclaw`, such as local plugin, extra skills, agent directory and session or cron store paths, pointing at the same location after `migrate` and `adopt import`, instead of letting them resolve under the new environment root. Other config text is left unchanged.
- Also keep `~/...` values in includes placed at a list position and in values containing a `${VAR}` placeholder that do not lead into `~/.openclaw` pointing at the same location after `migrate` and `adopt import`. Home-relative trust roots in `skills.load.allowSymlinkTargets` and `security.installPolicy.exec.trustedDirs`, placeholder values that lead into `~/.openclaw`, and `nodeHost.workerRuns.folders` entries stay as written, and migration now prints a warning naming each such setting, because they may resolve differently in the new environment.
- Name the environments whose Gateway port families hold a retained dev UI port when `ocm dev` refuses it, and offer `--no-ui`, instead of asking the user to release a port that no command can release. A port bound by a process keeps the existing "occupied or reserved" wording.

## 0.3.8 - 2026-10-29
Expand Down
44 changes: 30 additions & 14 deletions docs/USAGE.md
Original file line number Diff line number Diff line change
Expand Up @@ -1288,8 +1288,8 @@ still apply.
Migration and adoption also spell out home-relative config paths. A plain
install expands `~` against its home, but the new environment runs with
`OPENCLAW_HOME` set to its root, so OpenClaw expands `~` against the environment
root instead. Every setting OpenClaw expands `~` in that names a location outside
`~/.openclaw`, such as a local plugin in `plugins.load.paths`, a
root instead. Every setting OpenClaw expands `~` in that names a location
outside `~/.openclaw`, such as a local plugin in `plugins.load.paths`, a
`skills.load.extraDirs` entry, an agent directory or a session or cron store, is
therefore rewritten in `openclaw.json` and its copied includes to the absolute
path it named in the source home. These are the settings OpenClaw's runtime
Expand All @@ -1301,18 +1301,34 @@ including `skills.load.extraDirs`, `hooks.internal.load.extraDirs`, agent `cwd`,
handling and lexical `.` and `..` resolution as its OpenClaw consumer. Settings
OpenClaw expands against the OS home keep their meaning without a rewrite,
because the environment keeps your `HOME`. Other strings, such as message text,
stay as written. Not rewritten yet
([#972](https://github.com/openclaw/ocm/issues/972)): values containing a
`${VAR}` placeholder, which OpenClaw substitutes before resolving the path;
`nodeHost.workerRuns.folders`; values in an include placed at a list position;
and the trust roots `skills.load.allowSymlinkTargets` and
`security.installPolicy.exec.trustedDirs`, which keep failing closed. The source
home is your home directory when the migrated directory is your own
`~/.openclaw`, through any path that leads to it;
otherwise it is the directory that contains the migrated `.openclaw` as you
spelled it, or as it is stored when only that path is named `.openclaw`, or
your home directory for a state directory with another name. Values under
`~/.openclaw` already name the environment's copied state and stay as written.
stay as written. Path values in an include placed at a list position are
rewritten like any other. In a value containing a `${VAR}` placeholder, only the
directories before the first placeholder are resolved; OpenClaw substitutes the
placeholder and resolves the rest, as it would for the same value spelled from
your home, so a placeholder that itself leads into `~/.openclaw` names the
source's state directory. A placeholder value under `~/.openclaw`, or one that
reaches it through a symlink, stays as written, and migration prints a warning
naming its setting, because the placeholder may still lead elsewhere once
OpenClaw substitutes it. `nodeHost.workerRuns.folders` entries are not
rewritten: OpenClaw resolves them to real paths, through symlinks, and grants
workers access to the result, and migration can change the links and directories
they resolve through (for example when it imports a linked workspace), so no
spelling is known to keep the same folder. Their `~/...` entries now resolve
under the environment root, and migration prints a warning naming the setting.
Replace them with absolute paths to keep the original folders
([#972](https://github.com/openclaw/ocm/issues/972)). The trust roots
`skills.load.allowSymlinkTargets` and `security.installPolicy.exec.trustedDirs`
are not rewritten, because that would change what the new environment trusts:
their `~/...` entries outside `~/.openclaw` now resolve under the environment
root, and migration prints a warning naming each such setting. Replace those
entries with absolute paths to keep trusting the original directories. In these
warnings, `[]` in a setting name stands for a list entry. The source home is
your home directory when the migrated directory is your own `~/.openclaw`,
through any path that leads to it; otherwise it is the directory that contains
the migrated `.openclaw` as you spelled it, or as it is stored when only that
path is named `.openclaw`, or your home directory for a state directory with
another name. Values under `~/.openclaw` already name the environment's copied
state and stay as written.
Clone and archive import keep `~/...` values unchanged, because their source
already expands `~` against an environment root.

Expand Down
29 changes: 29 additions & 0 deletions src/cli/migrate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,10 @@ impl Cli {
result.sandbox_port,
);
self.warn_external_migration_plugins(&result.external_plugin_ids);
self.warn_kept_home_relative_settings(
&result.summary.name,
&result.kept_home_relative_settings,
);
let summary = result.summary;

if json_flag {
Expand Down Expand Up @@ -211,6 +215,10 @@ impl Cli {
result.sandbox_port,
);
self.warn_external_migration_plugins(&result.external_plugin_ids);
self.warn_kept_home_relative_settings(
&result.summary.name,
&result.kept_home_relative_settings,
);
let summary = result.summary;

if json_flag {
Expand Down Expand Up @@ -243,6 +251,27 @@ impl Cli {
}
}

fn warn_kept_home_relative_settings(
&self,
env_name: &str,
settings: &[crate::store::KeptHomeRelativeSetting],
) {
for kept in settings {
let setting = &kept.setting;
self.stderr_line(match kept.reason {
crate::store::KeptHomeRelativeReason::TrustRoot => format!(
"warning: env {env_name} keeps the ~/ entries of trust root {setting} as written; they now resolve under the env root instead of the source home, so OCM did not carry that trust over. Replace them with absolute paths to keep trusting the original directories"
),
crate::store::KeptHomeRelativeReason::Placeholder => format!(
"warning: env {env_name} keeps the ~/ entries of {setting} that contain a ${{VAR}} placeholder and point into ~/.openclaw as written; if a placeholder leads them outside ~/.openclaw, they now resolve under the env root instead of the source home. Replace such entries with absolute paths"
),
crate::store::KeptHomeRelativeReason::WorkerFolder => format!(
"warning: env {env_name} keeps the ~/ entries of {setting} as written; OpenClaw now resolves them against the env root, so workers may be allowed or refused different folders than before. Replace them with absolute paths to keep the original folders"
),
});
}
}

fn handle_adopt_inspect(&self, args: Vec<String>) -> Result<i32, String> {
let (args, json_flag, profile) = self.consume_human_output_flags(args, "adopt inspect")?;
if args.len() > 1 {
Expand Down
5 changes: 5 additions & 0 deletions src/migrate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ pub(crate) struct MigrateHomeResult {
pub cleared_sandbox_origin: bool,
pub sandbox_port: Option<u32>,
pub external_plugin_ids: Vec<String>,
pub kept_home_relative_settings: Vec<crate::store::KeptHomeRelativeSetting>,
}

#[derive(Clone, Debug)]
Expand Down Expand Up @@ -353,6 +354,7 @@ struct CompletedMigrationImport {
cleared_sandbox_origin: bool,
sandbox_port: Option<u32>,
external_plugin_ids: Vec<String>,
kept_home_relative_settings: Vec<crate::store::KeptHomeRelativeSetting>,
}

pub fn default_migration_source_home(env: &BTreeMap<String, String>) -> PathBuf {
Expand Down Expand Up @@ -518,6 +520,7 @@ fn migrate_plain_openclaw_home_inner(
cleared_sandbox_origin: completed.cleared_sandbox_origin,
sandbox_port: completed.sandbox_port,
external_plugin_ids: completed.external_plugin_ids,
kept_home_relative_settings: completed.kept_home_relative_settings,
})
}

Expand Down Expand Up @@ -646,6 +649,7 @@ fn complete_migration_import(
cleared_sandbox_origin: config_rewrite.cleared_sandbox_origin,
sandbox_port: config_rewrite.sandbox_port,
external_plugin_ids: runtime_state.external_plugin_ids,
kept_home_relative_settings: config_rewrite.kept_home_relative_settings,
});
};

Expand All @@ -671,6 +675,7 @@ fn complete_migration_import(
cleared_sandbox_origin: config_rewrite.cleared_sandbox_origin,
sandbox_port: config_rewrite.sandbox_port,
external_plugin_ids: runtime_state.external_plugin_ids,
kept_home_relative_settings: config_rewrite.kept_home_relative_settings,
}),
Err(error) => {
let rollback = rollback_migrated_launcher(created_launcher.as_deref(), env, cwd);
Expand Down
18 changes: 9 additions & 9 deletions src/store/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,15 +84,15 @@ pub(crate) use layout::{
supervisor_child_log_file,
};
pub(crate) use openclaw_config::{
GatewayReadinessHost, MinimumLocalConfig, OpenClawConfigAudit, OpenClawConfigRepair,
audit_openclaw_config, clear_skip_bootstrap_for_openclaw_onboarding, dev_ui_gateway_base_path,
dev_ui_gateway_url, ensure_minimum_local_openclaw_config, gateway_readiness_certificate,
gateway_readiness_host, gateway_url_for_launch, loopback_gateway_url,
normalize_new_environment_sandbox_origin, openclaw_config_include_paths,
openclaw_config_uses_includes, prepare_minimum_local_openclaw_config,
prepare_openclaw_config_repair, reject_include_owned_agent_workspaces,
reject_include_owned_sandbox_origin, rewrite_copied_config_include_directives,
rewrite_external_workspace_paths_for_migration,
GatewayReadinessHost, KeptHomeRelativeReason, KeptHomeRelativeSetting, MinimumLocalConfig,
OpenClawConfigAudit, OpenClawConfigRepair, audit_openclaw_config,
clear_skip_bootstrap_for_openclaw_onboarding, dev_ui_gateway_base_path, dev_ui_gateway_url,
ensure_minimum_local_openclaw_config, gateway_readiness_certificate, gateway_readiness_host,
gateway_url_for_launch, loopback_gateway_url, normalize_new_environment_sandbox_origin,
openclaw_config_include_paths, openclaw_config_uses_includes,
prepare_minimum_local_openclaw_config, prepare_openclaw_config_repair,
reject_include_owned_agent_workspaces, reject_include_owned_sandbox_origin,
rewrite_copied_config_include_directives, rewrite_external_workspace_paths_for_migration,
rewrite_identity_bound_workspace_paths_for_target, rewrite_openclaw_config_for_migration,
rewrite_openclaw_config_for_new_environment, rewrite_openclaw_config_for_simulation,
rewrite_openclaw_config_for_target, rewrite_openclaw_config_includes_for_target,
Expand Down
Loading
Loading