diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 628c33aa8684..7124d08563da 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -1883,6 +1883,9 @@ jobs:
OPENCLAW_NODE_TEST_ENV_JSON: ${{ toJson(matrix.env) }}
OPENCLAW_NODE_TEST_INCLUDE_PATTERNS_JSON: ${{ toJson(matrix.includePatterns) }}
OPENCLAW_NODE_TEST_TARGETS_JSON: ${{ toJson(matrix.targets) }}
+ # Frozen targets can carry integration hooks whose cold setup exceeds
+ # the current 120-second default on shared release runners.
+ OPENCLAW_NODE_TEST_VITEST_ARGS_JSON: ${{ needs.preflight.outputs.compatibility_target == 'true' && '["--hookTimeout=300000"]' || '[]' }}
OPENCLAW_VITEST_SHARD_NAME: ${{ matrix.shard_name }}
OPENCLAW_VITEST_NO_OUTPUT_TIMEOUT_MS: "300000"
OPENCLAW_VITEST_NO_OUTPUT_RETRY: "1"
diff --git a/apps/android/app/src/main/res/values-ja/strings.xml b/apps/android/app/src/main/res/values-ja/strings.xml
index a8b9a9d88416..93f54cc3e947 100644
--- a/apps/android/app/src/main/res/values-ja/strings.xml
+++ b/apps/android/app/src/main/res/values-ja/strings.xml
@@ -1050,7 +1050,7 @@
"未ペアリング"
"Gatewayがこのスマートフォンを認識しました"
"設定済みのモデルはありません"
- "無効にする"
+ "無効化"
"アプリの言語"
"Gatewayをペアリング中"
"保存済みの認証情報が無効です"
diff --git a/scripts/ci-run-node-test-shard.mjs b/scripts/ci-run-node-test-shard.mjs
index 7f6c0330f9c0..42ec776b91d5 100644
--- a/scripts/ci-run-node-test-shard.mjs
+++ b/scripts/ci-run-node-test-shard.mjs
@@ -17,6 +17,7 @@ const FS_MODULE_CACHE_PATH_ENV_KEY = "OPENCLAW_VITEST_FS_MODULE_CACHE_PATH";
const FS_MODULE_CACHE_WRITER_ENV_KEY = "OPENCLAW_VITEST_FS_MODULE_CACHE_WRITER";
const NODE_COMPILE_CACHE_PATH_ENV_KEY = "NODE_COMPILE_CACHE";
const NODE_COMPILE_CACHE_WRITER_ENV_KEY = "OPENCLAW_NODE_COMPILE_CACHE_WRITER";
+const VITEST_EXTRA_ARGS_ENV_KEY = "OPENCLAW_NODE_TEST_VITEST_ARGS_JSON";
const FS_MODULE_CACHE_MAX_BYTES = 2 * 1024 * 1024 * 1024;
const NODE_COMPILE_CACHE_MAX_BYTES = 1024 * 1024 * 1024;
const FS_MODULE_CACHE_PRUNE_TARGET_RATIO = 0.75;
@@ -220,6 +221,7 @@ function runChild(args, childEnv, label) {
export async function runShardPlans(plans, options = {}) {
const baseEnv = options.env ?? process.env;
+ const vitestExtraArgs = parseJsonEnv(baseEnv, VITEST_EXTRA_ARGS_ENV_KEY, []);
const concurrency = Math.max(1, options.concurrency ?? PLAN_CONCURRENCY);
const runner = options.runChild ?? runChild;
const scratchDir = options.scratchDir ?? mkdtempSync(join(tmpdir(), "openclaw-node-shard-"));
@@ -233,12 +235,16 @@ export async function runShardPlans(plans, options = {}) {
const index = nextIndex;
nextIndex += 1;
const entry = plans[index];
- const args = entry.kind === "target" ? [entry.target] : entry.plan.configs;
- if (!Array.isArray(args) || args.length === 0) {
+ const targetArgs = entry.kind === "target" ? [entry.target] : entry.plan.configs;
+ if (!Array.isArray(targetArgs) || targetArgs.length === 0) {
console.error(`Missing node test shard configs for ${entry.name}`);
exitCode = exitCode || 1;
return;
}
+ const args =
+ Array.isArray(vitestExtraArgs) && vitestExtraArgs.length > 0
+ ? [...targetArgs, "--", ...vitestExtraArgs]
+ : targetArgs;
const childEnv = buildChildEnv(entry, baseEnv, scratchDir, index, {
serial: concurrency === 1,
cacheSlot,
diff --git a/test/scripts/ci-run-node-test-shard.test.ts b/test/scripts/ci-run-node-test-shard.test.ts
index c5dcd08e3b52..2b2bbd97950f 100644
--- a/test/scripts/ci-run-node-test-shard.test.ts
+++ b/test/scripts/ci-run-node-test-shard.test.ts
@@ -152,6 +152,30 @@ describe("scripts/ci-run-node-test-shard.mjs", () => {
expect(new Set(seen.map((run) => run.cache)).size).toBe(3);
});
+ it("forwards trusted Vitest arguments after the target separator", async () => {
+ const scratchDir = makeScratchDir();
+ const seen: string[][] = [];
+ const exitCode = await runShardPlans(
+ resolveShardPlans({
+ OPENCLAW_NODE_TEST_CONFIGS_JSON: JSON.stringify(["test/vitest/vitest.unit.config.ts"]),
+ }),
+ {
+ concurrency: 1,
+ env: {
+ OPENCLAW_NODE_TEST_VITEST_ARGS_JSON: JSON.stringify(["--hookTimeout=300000"]),
+ },
+ runChild: async (args: string[]) => {
+ seen.push(args);
+ return 0;
+ },
+ scratchDir,
+ },
+ );
+
+ expect(exitCode).toBe(0);
+ expect(seen).toEqual([["test/vitest/vitest.unit.config.ts", "--", "--hookTimeout=300000"]]);
+ });
+
it("reuses isolated persistent cache slots across serial work", async () => {
const scratchDir = makeScratchDir();
const persistentRoot = path.join(makeScratchDir(), "persistent");
diff --git a/test/scripts/ci-workflow-guards.test.ts b/test/scripts/ci-workflow-guards.test.ts
index 003919912a7f..6a10af4719de 100644
--- a/test/scripts/ci-workflow-guards.test.ts
+++ b/test/scripts/ci-workflow-guards.test.ts
@@ -3800,6 +3800,9 @@ printf '%s\n' "\${CURL_SUCCESS_IP:-203.0.113.7}"
expect(runStep.env.OPENCLAW_VITEST_NO_OUTPUT_RETRY).toBe("1");
expect(runStep.env.OPENCLAW_NODE_TEST_ENV_JSON).toBe("${{ toJson(matrix.env) }}");
expect(runStep.env.OPENCLAW_NODE_TEST_TARGETS_JSON).toBe("${{ toJson(matrix.targets) }}");
+ expect(runStep.env.OPENCLAW_NODE_TEST_VITEST_ARGS_JSON).toBe(
+ "${{ needs.preflight.outputs.compatibility_target == 'true' && '[\"--hookTimeout=300000\"]' || '[]' }}",
+ );
expect(runStep.env.JOB_CONTEXT_JSON).toBe("${{ toJSON(job) }}");
// Shard execution policy lives in the unit-tested wrapper script. Frozen
// release targets load that wrapper from the exact trusted workflow SHA.
diff --git a/test/scripts/package-acceptance-workflow.test.ts b/test/scripts/package-acceptance-workflow.test.ts
index 5b807716b49f..39c05f7afc93 100644
--- a/test/scripts/package-acceptance-workflow.test.ts
+++ b/test/scripts/package-acceptance-workflow.test.ts
@@ -2385,7 +2385,7 @@ describe("package artifact reuse", () => {
});
expect(dockerAcceptanceJob.with).toMatchObject({
allow_frozen_target_scenario_omissions:
- "${{ inputs.allow_frozen_target_scenario_omissions }}",
+ "${{ inputs.allow_frozen_target_scenario_omissions || false }}",
});
expect(workflow).toContain(
"live_repo_e2e_release_checks:\n name: Run repo/live E2E validation\n needs: [resolve_target]",