Skip to content

Commit 5881b01

Browse files
committed
test(sandbox): cover registry migration
1 parent 4afc800 commit 5881b01

4 files changed

Lines changed: 81 additions & 0 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ Docs: https://docs.openclaw.ai
1717
- Plugins/update: on the beta OpenClaw update channel, default-line npm and ClawHub plugin updates try `@beta` first and fall back to default/latest when no plugin beta release exists.
1818
- Channels/WhatsApp: support explicit WhatsApp Channel/Newsletter `@newsletter` outbound message targets with channel session metadata instead of DM routing. Fixes #13417; carries forward the narrow outbound target idea from #13424. Thanks @vincentkoc and @agentz-manfred.
1919
- Exec approvals: add a tree-sitter-backed shell command explainer for future approval and command-review surfaces. (#75004) Thanks @jesse-merhi.
20+
- Agents/sandbox: store sandbox container and browser registry entries as per-runtime shard files, reducing unrelated session lock contention while `openclaw doctor --fix` migrates legacy monolithic registry files. (#74831) Thanks @luckylhb90.
2021

2122
### Fixes
2223

docs/cli/doctor.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ Notes:
5757
- Doctor warns when Codex-mode agents are configured and personal Codex CLI assets exist in the operator's Codex home. Local Codex app-server launches use isolated per-agent homes, so use `openclaw migrate codex --dry-run` to inventory assets that should be promoted deliberately.
5858
- Doctor warns when skills allowed for the default agent are unavailable in the current runtime environment because bins, env vars, config, or OS requirements are missing. `doctor --fix` can disable those unavailable skills with `skills.entries.<skill>.enabled=false`; install/configure the missing requirement instead when you want to keep the skill active.
5959
- If sandbox mode is enabled but Docker is unavailable, doctor reports a high-signal warning with remediation (`install Docker` or `openclaw config set agents.defaults.sandbox.mode off`).
60+
- If legacy sandbox registry files (`~/.openclaw/sandbox/containers.json` or `~/.openclaw/sandbox/browsers.json`) are present, doctor reports them; `openclaw doctor --fix` migrates valid entries into sharded registry directories and quarantines invalid legacy files.
6061
- If `gateway.auth.token`/`gateway.auth.password` are SecretRef-managed and unavailable in the current command path, doctor reports a read-only warning and does not write plaintext fallback credentials.
6162
- If channel SecretRef inspection fails in a fix path, doctor continues and reports a warning instead of exiting early.
6263
- After state-directory migrations, doctor warns when enabled default Telegram or Discord accounts depend on env fallback and `TELEGRAM_BOT_TOKEN` or `DISCORD_BOT_TOKEN` is unavailable to the doctor process.

docs/cli/sandbox.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,15 @@ Use `openclaw sandbox recreate` to force removal of old runtimes. They are recre
164164
Prefer `openclaw sandbox recreate` over manual backend-specific cleanup. It uses the Gateway's runtime registry and avoids mismatches when scope or session keys change.
165165
</Tip>
166166

167+
## Registry migration
168+
169+
OpenClaw stores sandbox runtime metadata as one JSON shard per container/browser entry under the sandbox state directory. Older installs may still have monolithic legacy files:
170+
171+
- `~/.openclaw/sandbox/containers.json`
172+
- `~/.openclaw/sandbox/browsers.json`
173+
174+
Regular sandbox runtime reads do not rewrite those files. Run `openclaw doctor --fix` to migrate valid legacy entries into the sharded registry directories. Invalid legacy files are quarantined so one bad old registry cannot hide current runtime entries.
175+
167176
## Configuration
168177

169178
Sandbox settings live in `~/.openclaw/openclaw.json` under `agents.defaults.sandbox` (per-agent overrides go in `agents.list[].sandbox`):

src/agents/sandbox/registry.test.ts

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,22 @@ async function seedContainerRegistry(entries: SandboxRegistryEntry[]) {
172172
await fs.writeFile(SANDBOX_REGISTRY_PATH, `${JSON.stringify({ entries }, null, 2)}\n`, "utf-8");
173173
}
174174

175+
async function seedBrowserRegistry(entries: SandboxBrowserRegistryEntry[]) {
176+
await fs.writeFile(
177+
SANDBOX_BROWSER_REGISTRY_PATH,
178+
`${JSON.stringify({ entries }, null, 2)}\n`,
179+
"utf-8",
180+
);
181+
}
182+
183+
async function seedStaleLock(lockPath: string) {
184+
await fs.writeFile(
185+
lockPath,
186+
`${JSON.stringify({ pid: 999_999_999, createdAt: "2000-01-01T00:00:00.000Z" })}\n`,
187+
"utf-8",
188+
);
189+
}
190+
175191
describe("registry race safety", () => {
176192
it("does not migrate legacy registry files from runtime reads", async () => {
177193
await seedContainerRegistry([containerEntry({ containerName: "legacy-container" })]);
@@ -204,6 +220,60 @@ describe("registry race safety", () => {
204220
]);
205221
});
206222

223+
it("migrates legacy container and browser registry files after explicit repair", async () => {
224+
await seedContainerRegistry([
225+
containerEntry({
226+
containerName: "legacy-container",
227+
sessionKey: "agent:legacy",
228+
lastUsedAtMs: 7,
229+
configHash: "legacy-container-hash",
230+
}),
231+
]);
232+
await seedBrowserRegistry([
233+
browserEntry({
234+
containerName: "legacy-browser",
235+
sessionKey: "agent:legacy",
236+
cdpPort: 9333,
237+
noVncPort: 6081,
238+
configHash: "legacy-browser-hash",
239+
}),
240+
]);
241+
await seedStaleLock(`${SANDBOX_REGISTRY_PATH}.lock`);
242+
await seedStaleLock(`${SANDBOX_BROWSER_REGISTRY_PATH}.lock`);
243+
244+
await expect(migrateLegacySandboxRegistryFiles()).resolves.toEqual([
245+
expect.objectContaining({ kind: "containers", status: "migrated", entries: 1 }),
246+
expect.objectContaining({ kind: "browsers", status: "migrated", entries: 1 }),
247+
]);
248+
249+
await expect(fs.access(SANDBOX_REGISTRY_PATH)).rejects.toThrow();
250+
await expect(fs.access(SANDBOX_BROWSER_REGISTRY_PATH)).rejects.toThrow();
251+
await expect(fs.access(`${SANDBOX_REGISTRY_PATH}.lock`)).rejects.toThrow();
252+
await expect(fs.access(`${SANDBOX_BROWSER_REGISTRY_PATH}.lock`)).rejects.toThrow();
253+
await expect(readRegistry()).resolves.toEqual({
254+
entries: [
255+
expect.objectContaining({
256+
containerName: "legacy-container",
257+
backendId: "docker",
258+
runtimeLabel: "legacy-container",
259+
sessionKey: "agent:legacy",
260+
configHash: "legacy-container-hash",
261+
}),
262+
],
263+
});
264+
await expect(readBrowserRegistry()).resolves.toEqual({
265+
entries: [
266+
expect.objectContaining({
267+
containerName: "legacy-browser",
268+
sessionKey: "agent:legacy",
269+
cdpPort: 9333,
270+
noVncPort: 6081,
271+
configHash: "legacy-browser-hash",
272+
}),
273+
],
274+
});
275+
});
276+
207277
it("does not overwrite newer sharded entries during legacy migration", async () => {
208278
await updateRegistry(
209279
containerEntry({

0 commit comments

Comments
 (0)