Skip to content
Open
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
19 changes: 19 additions & 0 deletions src/gateway/r2.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,25 @@ describe('mountR2Storage', () => {
expect(console.error).toHaveBeenCalledWith('Failed to mount R2 bucket:', expect.any(Error));
});

it('returns true when mount fails with "already in use" error', async () => {
const { sandbox, mountBucketMock, startProcessMock } = createMockSandbox({ mounted: false });
mountBucketMock.mockRejectedValue(
new Error('InvalidMountConfigError: Mount path "/data/moltbot" is already in use by bucket "moltbot-data". Unmount the existing bucket first or use a different mount path.'),
);
startProcessMock.mockResolvedValueOnce(createMockProcess(''));

const env = createMockEnvWithR2();

const result = await mountR2Storage(sandbox, env);

expect(result).toBe(true);
expect(console.log).toHaveBeenCalledWith(
'R2 bucket already mounted at',
'/data/moltbot',
'(detected via mount error)',
);
});

it('returns true if mount fails but check shows it is actually mounted', async () => {
const { sandbox, mountBucketMock, startProcessMock } = createMockSandbox();
startProcessMock
Expand Down
6 changes: 6 additions & 0 deletions src/gateway/r2.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,12 @@ export async function mountR2Storage(sandbox: Sandbox, env: MoltbotEnv): Promise
const errorMessage = err instanceof Error ? err.message : String(err);
console.log('R2 mount error:', errorMessage);

// "already in use" means the bucket is mounted — treat as success
if (errorMessage.includes('already in use')) {
console.log('R2 bucket already mounted at', R2_MOUNT_PATH, '(detected via mount error)');
return true;
}

// Check again if it's mounted - the error might be misleading
if (await isR2Mounted(sandbox)) {
console.log('R2 bucket is mounted despite error');
Expand Down
6 changes: 5 additions & 1 deletion src/gateway/sync.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { describe, it, expect, beforeEach } from 'vitest';
import { describe, it, expect, beforeEach, vi } from 'vitest';
import { syncToR2 } from './sync';
import {
createMockEnv,
Expand All @@ -8,6 +8,10 @@ import {
suppressConsole,
} from '../test-utils';

vi.mock('./process', () => ({
ensureMoltbotGateway: vi.fn().mockResolvedValue({ id: 'mock-process' }),
}));

describe('syncToR2', () => {
beforeEach(() => {
suppressConsole();
Expand Down
13 changes: 13 additions & 0 deletions src/gateway/sync.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { Sandbox } from '@cloudflare/sandbox';
import type { MoltbotEnv } from '../types';
import { R2_MOUNT_PATH } from '../config';
import { ensureMoltbotGateway } from './process';
import { mountR2Storage } from './r2';
import { waitForProcess } from './utils';

Expand Down Expand Up @@ -35,6 +36,18 @@ export async function syncToR2(sandbox: Sandbox, env: MoltbotEnv): Promise<SyncR
return { success: false, error: 'R2 storage is not configured' };
}

// Ensure the gateway is running — the startup script creates the config
// file via `openclaw onboard`, which is required before we can sync
try {
await ensureMoltbotGateway(sandbox, env);
} catch (err) {
return {
success: false,
error: 'Failed to start gateway',
details: err instanceof Error ? err.message : 'Unknown error',
};
}

// Mount R2 if not already mounted
const mounted = await mountR2Storage(sandbox, env);
if (!mounted) {
Expand Down