Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
edfcadb
fix(ci): deflake the idle-watchdog env-parse script test
yiliang114 Aug 12, 2026
13a61cb
Merge branch 'main' into fix/ci-idle-parse-guard-flake
wenshao Aug 12, 2026
21c00e1
fix(ci): size test thread pools to the machine and retry load flakes
yiliang114 Aug 12, 2026
0c51f42
Merge branch 'fix/ci-test-load-shaping' into fix/ci-idle-parse-guard-…
yiliang114 Aug 12, 2026
1b7954f
Merge remote-tracking branch 'origin/fix/ci-idle-parse-guard-flake' i…
yiliang114 Aug 12, 2026
d94ea47
fix(ci): restore the 0-boundary probe and right-size the deflake comm…
yiliang114 Aug 12, 2026
bf10b2f
fix(ci): remove dead pool-resizing config and pin idle-timeout guard
yiliang114 Aug 12, 2026
23f74f2
fix(ci): capture the transient ENOSPC and shrink the manifest suite's…
yiliang114 Aug 12, 2026
8290974
test(ci): pin the idle-timeout parse guard's source text for the NaN …
yiliang114 Aug 12, 2026
0870ffb
fix(ci): sample available memory alongside the ENOSPC diagnostics
yiliang114 Aug 12, 2026
97b7847
fix(ci): limit retries to script tests
yiliang114 Aug 12, 2026
aa56a5e
Merge remote-tracking branch 'origin/main' into fix/ci-idle-parse-gua…
yiliang114 Aug 12, 2026
ea58ec9
fix(ci): route test temp files to the runner's disk-backed temp area
yiliang114 Aug 12, 2026
8c38174
test(core): support long temporary workspace paths
yiliang114 Aug 12, 2026
14bef0b
fix(ci): keep routed temp paths socket-safe
yiliang114 Aug 12, 2026
29c346a
fix(ci): use real short Linux temp paths
yiliang114 Aug 12, 2026
0348f42
test(ci): remove remaining teardown races
yiliang114 Aug 12, 2026
9d4cd07
test(ci): harden temp routing regression
yiliang114 Aug 12, 2026
07a8252
test(ci): stop leaked server reconcilers
yiliang114 Aug 12, 2026
9708c2d
fix(ci): clean up test sampler reliably
yiliang114 Aug 12, 2026
6219dd9
Merge remote-tracking branch 'origin/main' into HEAD
yiliang114 Aug 12, 2026
a67c0bf
fix(review): keep skill context within manifest bounds
yiliang114 Aug 13, 2026
d4f7fcb
test(web-shell): wait for image ingestion completion
yiliang114 Aug 13, 2026
52d76a3
test(cli): avoid timed status line module import
yiliang114 Aug 13, 2026
5937b8d
test(ci): remove remaining load-sensitive waits
yiliang114 Aug 13, 2026
a33f81e
fix(ci): cap test forks on shared runners
yiliang114 Aug 13, 2026
254758b
fix(ci): keep temp cleanup from failing tests
yiliang114 Aug 13, 2026
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
72 changes: 72 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -507,7 +507,31 @@ jobs:
QWEN_DEFAULT_AUTH_TYPE: ''
run: |-
node -e "const fs = require('node:fs'); for (const key of ['HOME', 'USERPROFILE']) { const dir = process.env[key]; if (dir) fs.mkdirSync(dir, { recursive: true }); }"
# ENOSPC has failed test steps mid-suite while the host looks
# healthy afterwards — a transient spike, likely /tmp inodes or a
# tmpfs cap. Sample the routed temp filesystem every 10s so the
# failing run captures the spike, and dump full state on failure.
# Keep Linux temp paths real and short on disk-backed /var/tmp.
# Symlink aliases break tests that intentionally compare real paths.
export TMPDIR="${RUNNER_TEMP:-${TMPDIR:-/tmp}}"
Comment thread
yiliang114 marked this conversation as resolved.
Comment thread
yiliang114 marked this conversation as resolved.
if [ "${RUNNER_OS:-}" = "Linux" ]; then
QWEN_CI_TMPDIR="$(mktemp -d /var/tmp/qwen-ci-XXXXXX 2>/dev/null || true)"
Comment thread
yiliang114 marked this conversation as resolved.
if [ -n "$QWEN_CI_TMPDIR" ]; then
TMPDIR="$QWEN_CI_TMPDIR"
Comment thread
yiliang114 marked this conversation as resolved.
export TMPDIR
trap 'rm -rf "$TMPDIR" 2>/dev/null || true' EXIT
fi
fi
( while true; do echo "DFSAMPLE $(date -u +%H:%M:%S 2>/dev/null) tmpdir[${TMPDIR}] space[$(df -h "${TMPDIR}" 2>/dev/null | tail -1)] inodes[$(df -i "${TMPDIR}" 2>/dev/null | tail -1)] memavail[$(awk '/MemAvailable/ {print $2, $3}' /proc/meminfo 2>/dev/null)]" 2>/dev/null; sleep 10; done ) &
SAMPLER_PID=$!
set +e
npm run test:ci
RC=$?
set -e
pkill -TERM -P "$SAMPLER_PID" 2>/dev/null || true
kill "$SAMPLER_PID" 2>/dev/null || true
Comment thread
yiliang114 marked this conversation as resolved.
if [ "$RC" -ne 0 ]; then df -hT 2>/dev/null || df -h 2>/dev/null || true; df -i 2>/dev/null || true; grep -E 'MemTotal|MemAvailable|SwapTotal|SwapFree' /proc/meminfo 2>/dev/null || true; fi
exit "$RC"
Comment thread
yiliang114 marked this conversation as resolved.

# Release guard for the Chrome extension: build, package, and scan real
# artifacts for forbidden adapter signatures instead of leaving
Expand Down Expand Up @@ -805,7 +829,31 @@ jobs:
QWEN_DEFAULT_AUTH_TYPE: ''
run: |-
node -e "const fs = require('node:fs'); for (const key of ['HOME', 'USERPROFILE']) { const dir = process.env[key]; if (dir) fs.mkdirSync(dir, { recursive: true }); }"
# ENOSPC has failed test steps mid-suite while the host looks
# healthy afterwards — a transient spike, likely /tmp inodes or a
# tmpfs cap. Sample the routed temp filesystem every 10s so the
# failing run captures the spike, and dump full state on failure.
# Keep Linux temp paths real and short on disk-backed /var/tmp.
# Symlink aliases break tests that intentionally compare real paths.
export TMPDIR="${RUNNER_TEMP:-${TMPDIR:-/tmp}}"
if [ "${RUNNER_OS:-}" = "Linux" ]; then
QWEN_CI_TMPDIR="$(mktemp -d /var/tmp/qwen-ci-XXXXXX 2>/dev/null || true)"
if [ -n "$QWEN_CI_TMPDIR" ]; then
TMPDIR="$QWEN_CI_TMPDIR"
export TMPDIR
trap 'rm -rf "$TMPDIR" 2>/dev/null || true' EXIT
fi
fi
( while true; do echo "DFSAMPLE $(date -u +%H:%M:%S 2>/dev/null) tmpdir[${TMPDIR}] space[$(df -h "${TMPDIR}" 2>/dev/null | tail -1)] inodes[$(df -i "${TMPDIR}" 2>/dev/null | tail -1)] memavail[$(awk '/MemAvailable/ {print $2, $3}' /proc/meminfo 2>/dev/null)]" 2>/dev/null; sleep 10; done ) &
SAMPLER_PID=$!
set +e
npm run test:ci
RC=$?
set -e
pkill -TERM -P "$SAMPLER_PID" 2>/dev/null || true
kill "$SAMPLER_PID" 2>/dev/null || true
if [ "$RC" -ne 0 ]; then df -hT 2>/dev/null || df -h 2>/dev/null || true; df -i 2>/dev/null || true; grep -E 'MemTotal|MemAvailable|SwapTotal|SwapFree' /proc/meminfo 2>/dev/null || true; fi
exit "$RC"

# Windows counterpart of test_macos (see that job's note). ECS is the default
# with a windows-2022 kill-switch fallback; the check name stays unchanged so
Expand Down Expand Up @@ -911,7 +959,31 @@ jobs:
QWEN_DEFAULT_AUTH_TYPE: ''
run: |-
node -e "const fs = require('node:fs'); for (const key of ['HOME', 'USERPROFILE']) { const dir = process.env[key]; if (dir) fs.mkdirSync(dir, { recursive: true }); }"
# ENOSPC has failed test steps mid-suite while the host looks
# healthy afterwards — a transient spike, likely /tmp inodes or a
# tmpfs cap. Sample the routed temp filesystem every 10s so the
# failing run captures the spike, and dump full state on failure.
# Keep Linux temp paths real and short on disk-backed /var/tmp.
# Symlink aliases break tests that intentionally compare real paths.
export TMPDIR="${RUNNER_TEMP:-${TMPDIR:-/tmp}}"
if [ "${RUNNER_OS:-}" = "Linux" ]; then
QWEN_CI_TMPDIR="$(mktemp -d /var/tmp/qwen-ci-XXXXXX 2>/dev/null || true)"
if [ -n "$QWEN_CI_TMPDIR" ]; then
TMPDIR="$QWEN_CI_TMPDIR"
export TMPDIR
trap 'rm -rf "$TMPDIR" 2>/dev/null || true' EXIT
fi
fi
( while true; do echo "DFSAMPLE $(date -u +%H:%M:%S 2>/dev/null) tmpdir[${TMPDIR}] space[$(df -h "${TMPDIR}" 2>/dev/null | tail -1)] inodes[$(df -i "${TMPDIR}" 2>/dev/null | tail -1)] memavail[$(awk '/MemAvailable/ {print $2, $3}' /proc/meminfo 2>/dev/null)]" 2>/dev/null; sleep 10; done ) &
SAMPLER_PID=$!
set +e
npm run test:ci
RC=$?
set -e
pkill -TERM -P "$SAMPLER_PID" 2>/dev/null || true
kill "$SAMPLER_PID" 2>/dev/null || true
if [ "$RC" -ne 0 ]; then df -hT 2>/dev/null || df -h 2>/dev/null || true; df -i 2>/dev/null || true; grep -E 'MemTotal|MemAvailable|SwapTotal|SwapFree' /proc/meminfo 2>/dev/null || true; fi
exit "$RC"

post_coverage_comment:
name: 'Post Coverage Comment'
Expand Down
1 change: 0 additions & 1 deletion .qwen/review-context.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@
},
{
"paths": ["packages/core/src/skills/**"],
"relatedPaths": ["packages/core/src/skills/**"],
"domains": ["core-skills"]
},
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,6 @@ const expectedManifest = {
},
{
paths: ['packages/core/src/skills/**'],
relatedPaths: ['packages/core/src/skills/**'],
domains: ['core-skills'],
},
{
Expand Down Expand Up @@ -95,8 +94,6 @@ const expectedManifest = {

const relatedPathSentinels: Readonly<Record<string, string>> = {
'packages/core/src/config/**': 'packages/core/src/config/config.ts',
'packages/core/src/skills/**':
'packages/core/src/skills/bundled/review/SKILL.md',
'packages/web-shell/client/adapters/**':
'packages/web-shell/client/adapters/types.ts',
'packages/web-shell/client/completions/**':
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,31 +15,35 @@ import {
} from 'node:fs';
import { tmpdir } from 'node:os';
import { dirname, join } from 'node:path';
import { afterAll, describe, expect, it } from 'vitest';
import { afterEach, describe, expect, it } from 'vitest';
import {
manifestRepositoryContextProvider,
MAX_GLOB_CANDIDATES,
MAX_MATCH_WORK,
} from './manifest-repository-context.js';
import { MAX_IDENTITY_BYTES } from './repository-context.js';

const worktrees: string[] = [];
let worktrees: string[] = [];

function temp(): string {
const root = realpathSync(mkdtempSync(join(tmpdir(), 'manifest-context-')));
worktrees.push(root);
return root;
}

// Several fixtures hold 16k-entry trees; leaking them exhausts a tmpfs
// /tmp within a handful of runs. Deleting them is tens of thousands of
// unlinks, which has blown past the default 10s hook timeout on a loaded
// CI runner — give the teardown the time it needs rather than failing a
// green suite on cleanup.
afterAll(() => {
// Several fixtures hold 16k-entry trees, and the skip-set suite stacks ten
// of them — holding every tree until afterAll keeps ~164k files (inodes)
// alive for the whole file, which on a shared self-hosted host coincides
// with concurrent jobs' temp files and has surfaced as ENOSPC mid-suite.
// Tear down per test instead so at most one 16k tree is live at a time.
// Deleting one tree is tens of thousands of unlinks, which has blown past
// the default 10s hook timeout on a loaded CI runner — give the teardown
// the time it needs rather than failing a green suite on cleanup.
afterEach(() => {
for (const root of worktrees) {
rmSync(root, { recursive: true, force: true });
}
Comment thread
yiliang114 marked this conversation as resolved.
worktrees = [];
}, 120_000);
Comment thread
yiliang114 marked this conversation as resolved.

function write(path: string, content = ''): void {
Expand Down
22 changes: 13 additions & 9 deletions packages/cli/src/serve/bridge-file-system-adapter.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -700,17 +700,20 @@ describe('createBridgeFileSystemAdapter', () => {

it('rejects a Unix socket as an external text target', async () => {
if (process.platform === 'win32') return;
const socketPath = path.join(outsideDir, 'target.sock');
const server = createServer();
await new Promise<void>((resolve, reject) => {
server.once('error', reject);
server.listen(socketPath, resolve);
});
const adapter = createBridgeFileSystemAdapter(
buildFactory({ trusted: true }),
{ allowSameHostToolWritesOutsideWorkspace: true },
const socketDir = await fsp.mkdtemp(
path.join(os.tmpdir(), 'bridge-fs-socket-'),
);
const socketPath = path.join(socketDir, 'target.sock');
const server = createServer();
try {
await new Promise<void>((resolve, reject) => {
server.once('error', reject);
server.listen(socketPath, resolve);
});
const adapter = createBridgeFileSystemAdapter(
buildFactory({ trusted: true }),
{ allowSameHostToolWritesOutsideWorkspace: true },
);
await expect(
adapter.writeText({
path: socketPath,
Expand All @@ -722,6 +725,7 @@ describe('createBridgeFileSystemAdapter', () => {
expect(auditEmits).toHaveLength(1);
} finally {
await new Promise<void>((resolve) => server.close(() => resolve()));
await fsp.rm(socketDir, { recursive: true, force: true });
}
});

Expand Down
54 changes: 38 additions & 16 deletions packages/cli/src/serve/server.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import request from 'supertest';
import { WebSocket } from 'ws';
import { trace, type Span } from '@opentelemetry/api';
import {
createServeApp,
createServeApp as createServeAppImpl,
computeKeepaliveIntervalMs,
detectFromLoopback,
listWorkspaceSessionsForResponse,
Expand Down Expand Up @@ -292,6 +292,38 @@ const baseOpts: ServeOptions = {
mode: 'http-bridge',
};

Comment thread
yiliang114 marked this conversation as resolved.
// Direct app tests bypass runQwenServe's reconciler cleanup.
const createdApps = new Set<ReturnType<typeof createServeAppImpl>>();

function createServeApp(...args: Parameters<typeof createServeAppImpl>) {
const app = createServeAppImpl(...args);
createdApps.add(app);
return app;
}

function stopCreatedApps() {
Comment thread
yiliang114 marked this conversation as resolved.
for (const app of createdApps) {
(
app.locals as { stopExtensionGenerationReconciler?: () => void }
).stopExtensionGenerationReconciler?.();
}
createdApps.clear();
}

afterEach(stopCreatedApps);

it('stops extension generation reconcilers for direct app tests', () => {
const stopExtensionGenerationReconciler = vi.fn();
createdApps.add({
locals: { stopExtensionGenerationReconciler },
} as ReturnType<typeof createServeAppImpl>);

stopCreatedApps();

expect(stopExtensionGenerationReconciler).toHaveBeenCalledOnce();
expect(createdApps.size).toBe(0);
});

function fakeDaemonLog(): DaemonLogger {
return {
info: vi.fn(),
Expand Down Expand Up @@ -364,7 +396,11 @@ afterAll(async () => {
restoreEnv('QWEN_HOME', previousServerTestQwenHome);
restoreEnv('QWEN_RUNTIME_DIR', previousServerTestRuntimeDir);
resetHomeEnvBootstrapForTesting();
await fsp.rm(serverTestEnvironmentRoot, { recursive: true, force: true });
await fsp.rm(serverTestEnvironmentRoot, {
recursive: true,
force: true,
maxRetries: 3,
});
});

function deferred<T = void>(): {
Expand Down Expand Up @@ -26809,7 +26845,6 @@ describe('createServeApp ServeAppDeps.fsFactory wiring (#4175 PR 18)', () => {
}

it('parks a single-workspace registry on app.locals for the canonical primary workspace', async () => {
const { createServeApp } = await import('./server.js');
const app = createServeApp(
{
port: 0,
Expand Down Expand Up @@ -26850,7 +26885,6 @@ describe('createServeApp ServeAppDeps.fsFactory wiring (#4175 PR 18)', () => {
});

it('parks a default WorkspaceFileSystemFactory on app.locals when none is injected', async () => {
const { createServeApp } = await import('./server.js');
const app = createServeApp(
{
port: 0,
Expand All @@ -26872,7 +26906,6 @@ describe('createServeApp ServeAppDeps.fsFactory wiring (#4175 PR 18)', () => {
});

it('uses the injected fsFactory verbatim when supplied', async () => {
const { createServeApp } = await import('./server.js');
const sentinel = { forRequest: vi.fn(() => ({ marker: 'injected' })) };
const app = createServeApp(
{
Expand All @@ -26895,7 +26928,6 @@ describe('createServeApp ServeAppDeps.fsFactory wiring (#4175 PR 18)', () => {
});

it('threads production-style primary trust into the default runtime metadata', async () => {
const { createServeApp } = await import('./server.js');
const app = createServeApp(
{
port: 0,
Expand All @@ -26911,7 +26943,6 @@ describe('createServeApp ServeAppDeps.fsFactory wiring (#4175 PR 18)', () => {
});

it('threads primary runtime env metadata into the default registry runtime', async () => {
const { createServeApp } = await import('./server.js');
const primaryRuntimeEnv = {
mode: 'runtime-overlay',
overlayKeys: ['OPENAI_API_KEY'],
Expand All @@ -26933,7 +26964,6 @@ describe('createServeApp ServeAppDeps.fsFactory wiring (#4175 PR 18)', () => {
});

it('uses an injected workspace registry as the primary runtime source', async () => {
const { createServeApp } = await import('./server.js');
const runtime = makeInjectedWorkspaceRuntime();
const registry = createWorkspaceRegistry([runtime]);

Expand Down Expand Up @@ -27006,7 +27036,6 @@ describe('createServeApp ServeAppDeps.fsFactory wiring (#4175 PR 18)', () => {
});

it('accepts matching runtime deps when a workspace registry is injected', async () => {
const { createServeApp } = await import('./server.js');
const runtime = makeInjectedWorkspaceRuntime();
const registry = createWorkspaceRegistry([runtime]);

Expand All @@ -27030,8 +27059,6 @@ describe('createServeApp ServeAppDeps.fsFactory wiring (#4175 PR 18)', () => {
});

it('requires the Voice coordinator paired with runtime removal', async () => {
const { createServeApp } = await import('./server.js');

expect(() =>
createServeApp(
{
Expand All @@ -27048,8 +27075,6 @@ describe('createServeApp ServeAppDeps.fsFactory wiring (#4175 PR 18)', () => {
});

it('requires a live bridge provider when runtime generations can change', async () => {
const { createServeApp } = await import('./server.js');

expect(() =>
createServeApp(
{
Expand All @@ -27066,7 +27091,6 @@ describe('createServeApp ServeAppDeps.fsFactory wiring (#4175 PR 18)', () => {
});

it('uses the injected registry sender when client-MCP over WS is enabled', async () => {
const { createServeApp } = await import('./server.js');
const runtime = makeInjectedWorkspaceRuntime();
const registry = createWorkspaceRegistry([runtime]);

Expand Down Expand Up @@ -27122,7 +27146,6 @@ describe('createServeApp ServeAppDeps.fsFactory wiring (#4175 PR 18)', () => {
});

it('rejects conflicting runtime deps when a workspace registry is injected', async () => {
const { createServeApp } = await import('./server.js');
const runtime = makeInjectedWorkspaceRuntime();
const registry = createWorkspaceRegistry([runtime]);

Expand Down Expand Up @@ -27246,7 +27269,6 @@ describe('createServeApp ServeAppDeps.fsFactory wiring (#4175 PR 18)', () => {
});

it('default fsFactory is built with trusted=false (writes refused)', async () => {
const { createServeApp } = await import('./server.js');
const { isFsError } = await import('./fs/index.js');
const os = await import('node:os');
const tmp = await import('node:fs').then((m) =>
Expand Down
19 changes: 2 additions & 17 deletions packages/cli/src/ui/hooks/useStatusLine.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,12 @@
* SPDX-License-Identifier: Apache-2.0
*/

import {
describe,
it,
expect,
vi,
beforeAll,
beforeEach,
afterEach,
} from 'vitest';
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import { renderHook, act } from '@testing-library/react';
import * as child_process from 'child_process';
import { StreamingState } from '../types.js';
import type { StatusLinePresetReasoning } from '../statusLinePresets.js';
import { useStatusLine } from './useStatusLine.js';

const debugLogMock = vi.hoisted(() => ({
log: vi.fn(),
Expand Down Expand Up @@ -157,14 +150,6 @@ function setStatusLineConfig(
}

describe('useStatusLine', () => {
// Must import dynamically after mocks are set up
let useStatusLine: typeof import('./useStatusLine.js').useStatusLine;

beforeAll(async () => {
const mod = await import('./useStatusLine.js');
useStatusLine = mod.useStatusLine;
}, 20_000);

beforeEach(() => {
vi.useFakeTimers();
vi.clearAllMocks();
Expand Down
Loading
Loading