fix: stabilize release CI regression tests
Cross-platform packages / Validate source (push) Canceled after 0s
Cross-platform packages / Authorize desktop packaging (push) Canceled after 0s
Cross-platform packages / linux arm64 (push) Canceled after 0s
Cross-platform packages / macos arm64 (push) Canceled after 0s
Cross-platform packages / windows arm64 (push) Canceled after 0s
Cross-platform packages / linux x64 (push) Canceled after 0s
Cross-platform packages / macos x64 (push) Canceled after 0s
Cross-platform packages / windows x64 (push) Canceled after 0s
Cross-platform packages / Publish GitHub and OSS release (push) Canceled after 0s

Address Desktop CI 35849362694 failures with isolated OpenCode config dependencies, cleared IPC lookup counts, Supervisor default-off expectations, and polling-safe UI fixtures and timeouts.

Record the failed candidate CI and follow-up validation: 14 focused tests, typecheck, and lint passed. Product code, versions, and bilingual release notes are unchanged. The revised candidate requires approval and successful main-branch CI before tagging.
This commit is contained in:
mesalogo
2026-09-23 19:15:08 +08:00
parent bf8ece0590
commit 163e245b00
5 changed files with 78 additions and 14 deletions
@@ -62,3 +62,48 @@ Agent packaging and publication verification remain pending. No local production
build or package was run. No push, tag, remote synchronization, or publication
was performed during preparation. Approval must identify the exact candidate
commit and both bilingual note files before either release tag is created.
## Approved Candidate CI Attempt
The user approved `bf8ece05902509411454b27b742ebfec930005ad` and both bilingual
note files. On 2026-09-23 that exact commit was fast-forwarded to `main` on
`origin` and `github`; both remote refs were verified. GitHub SSH stalled, so
the GitHub push used HTTPS with the existing GitHub CLI keyring credentials.
No persistent Git configuration was changed. GitHub reported the account's
existing pull-request-rule bypass for this requested direct push.
- [Desktop main CI](https://github.com/mesalogo/goodbuddy/actions/runs/35849362694)
failed in the full suite: 4 files failed, 418 passed, 13 skipped; 10 tests
failed, 4,997 passed, 43 skipped. Type checks, lint, and the production build
did not execute after the test failure.
- [Agent main CI](https://github.com/mesalogo/goodbuddy/actions/runs/35849362799)
passed source validation and native Linux x64, Linux arm64, and Darwin arm64
deterministic compound-package verification with ephemeral test signatures.
- Neither release tag was created or pushed. No Desktop or Agent publication
workflow was started, and no production release assets were verified.
The follow-up changes are limited to tests. Two IPC tests clear the startup
settings-read count before measuring interactive request lookups. The settings
failure UI test now expects Supervisor to remain disabled. OpenCode isolation
tests prepare their real configuration dependencies in a dedicated temporary
directory instead of relying on a prior local package preparation; connection
failures now include their diagnostic detail. The large activity-list and
multi-page navigation and external knowledge binding tests have explicit
15-second budgets; the binding test also waits up to five seconds for its
resulting heading.
Its snapshot fixture now selects by library ID, and its instance list remains
available during polling instead of assuming a fixed number of asynchronous
reads. Both mocks reset between tests; the binding test waits for the
selected-library read.
These budgets address timeouts observed in the full CI run without removing
assertions or changing product behavior.
The approved source and bilingual notes remain unchanged. These test changes
need a revised candidate commit and approval before another release attempt;
the failed commit must not be tagged. No local production build, package,
external model request, or LoongArch build was performed.
Follow-up validation: the seven selected IPC/UI regression cases and all seven
OpenCode isolation cases passed locally. Type checks and lint passed. Full
remote CI and the production build still require a new approved commit; these
focused results do not satisfy that release gate.
@@ -1,10 +1,11 @@
// @vitest-environment node
import { createServer } from 'node:http'
import { mkdtemp, readFile, rm, writeFile } from 'node:fs/promises'
import { createRequire } from 'node:module'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import spawn from 'cross-spawn'
import { describe, expect, it } from 'vitest'
import { afterAll, beforeAll, describe, expect, it } from 'vitest'
import { runtimeSettingsInputSchema } from '../../shared/contracts'
import { RuntimeSettingsStore } from '../runtime-settings-store'
import { applyRuntimeSelection } from './runtime-selection'
@@ -19,6 +20,19 @@ const newId = '00000000-0000-4000-8000-000000000123'
const denial = "OpenCode's free tier can only be used from within OpenCode"
describe('OpenCode isolated configuration paths', () => {
let fixtureRoot: string
let bundledConfigPath: string
beforeAll(async () => {
fixtureRoot = await mkdtemp(join(tmpdir(), 'goodbuddy-opencode-config-isolation-'))
const { prepareBundledOpenCodeConfig } = createRequire(import.meta.url)(
'../../../build/opencode-config.cjs'
)
bundledConfigPath = prepareBundledOpenCodeConfig(fixtureRoot)
}, 60_000)
afterAll(async () => {
if (fixtureRoot) await rm(fixtureRoot, { recursive: true, force: true })
})
it.each(['default', 'profile', 'platform', 'external'] as const)(
'resolves persisted %s source independently of the global default', async (source) => {
const root = await mkdtemp(join(tmpdir(), 'goodbuddy-source-matrix-'))
@@ -95,7 +109,7 @@ describe('OpenCode isolated configuration paths', () => {
embedded: true, binaryPath, defaultWorkspace: root,
configPath: source === 'native-empty' ? '' : configPath,
sharedCacheRoot: join(root, 'runtime-cache'),
bundledConfigPath: join(process.cwd(), '.runtime-resources', 'opencode-config'),
bundledConfigPath,
...(source === 'profile' ? { modelProfile: {
id: newId, name: 'Local mock', baseUrl: baseURL, modelName: 'probe-model',
protocol: 'openai-chat-completions' as const, authentication: 'none' as const
@@ -122,7 +136,8 @@ describe('OpenCode isolated configuration paths', () => {
const controller = new AbortController()
const timeout = setTimeout(() => controller.abort(), 25_000)
try {
expect(await runtime.testConnection()).toMatchObject({ available: true })
const connection = await runtime.testConnection()
expect(connection, connection.detail).toMatchObject({ available: true })
const config = JSON.parse(launchEnvironment.OPENCODE_CONFIG_CONTENT!)
expect(config.model).toBe(source === 'profile' ? 'goodbuddy-openai-chat/probe-model' : undefined)
expect(launchEnvironment.OPENCODE_CONFIG).toBe(source === 'native-file' ? configPath : undefined)
+2
View File
@@ -9649,6 +9649,7 @@ describe('registerIpcHandlers agent terminal state', () => {
}
)
const event = trustedEvent(harness.webContents)
harness.getApplicationSettings.mockClear()
const request = {
requestId: '00000000-0000-4000-8000-000000000013',
conversationId: 'duplicate-request',
@@ -9737,6 +9738,7 @@ describe('registerIpcHandlers agent terminal state', () => {
getEnabledBuiltinMcpServerIds
}
)
harness.getApplicationSettings.mockClear()
harness.getApplicationSettings.mockReturnValueOnce(
applicationSettings
)
+1 -1
View File
@@ -759,7 +759,7 @@ describe('ActivityPanel', () => {
})
)
expect(screen.getByText('活动 500')).toBeInTheDocument()
})
}, 15_000)
it('shows totals without double-counting cache tokens', () => {
render(
+12 -10
View File
@@ -1139,6 +1139,8 @@ describe("App", () => {
delete document.documentElement.dataset.theme;
document.documentElement.style.colorScheme = "";
vi.clearAllMocks();
vi.mocked(api.knowledge.getSnapshot).mockReset();
vi.mocked(api.knowledge.externalInstancesList).mockReset();
vi.mocked(api.agent.respondQuestion).mockReset().mockResolvedValue();
vi.mocked(api.agent.respondApproval).mockReset().mockResolvedValue();
magicTodoStatusChangedListener = undefined;
@@ -7288,8 +7290,10 @@ describe("App", () => {
const created = { ...existing, id: '33333333-3333-4333-8333-333333333333', name: 'Remote handbook', external: { knowledgeBaseId: '33333333-3333-4333-8333-333333333333', instanceId: instance.id, provider: 'dify' as const, remoteKnowledgeBaseId: 'remote-1', remoteName: 'Remote handbook', commonConfig: { resultLimit: 6, requestTimeoutMs: 15000, maxSnippetCharacters: 4000 }, providerConfig: { provider: 'dify' as const, useDatasetDefaults: true as const }, lastVerifiedAt: '2026-09-12' } };
const initial = { libraries: [existing, excluded], selectedLibraryId: existing.id, sources: [], documents: [], graphNodes: [], graphRelations: [], evidence: [] };
const updated = { ...initial, libraries: [...initial.libraries, created], selectedLibraryId: created.id };
vi.mocked(api.knowledge.getSnapshot).mockResolvedValueOnce(initial).mockResolvedValueOnce(updated);
vi.mocked(api.knowledge.externalInstancesList).mockResolvedValueOnce([instance]).mockResolvedValueOnce([instance]);
vi.mocked(api.knowledge.getSnapshot).mockImplementation(async (libraryId) =>
libraryId === created.id ? updated : initial
);
vi.mocked(api.knowledge.externalInstancesList).mockResolvedValue([instance]);
vi.mocked(api.knowledge.externalCatalogList).mockResolvedValueOnce({ items: [{ id: 'remote-1', name: 'Remote handbook' }], hasMore: false });
vi.mocked(api.knowledge.externalCatalogGet).mockResolvedValueOnce({ id: 'remote-1', name: 'Remote handbook' });
vi.mocked(api.knowledge.externalBindingsCreate).mockResolvedValueOnce(updated);
@@ -7306,15 +7310,15 @@ describe("App", () => {
fireEvent.click(screen.getByRole('button', { name: '测试检索' }));
await waitFor(() => expect(screen.getByRole('button', { name: '添加知识库' })).toBeEnabled());
fireEvent.click(screen.getByRole('button', { name: '添加知识库' }));
await screen.findByRole('heading', { name: 'Remote handbook' });
expect(api.knowledge.getSnapshot).toHaveBeenLastCalledWith(created.id);
await screen.findByRole('heading', { name: 'Remote handbook' }, { timeout: 5_000 });
await waitFor(() => expect(api.knowledge.getSnapshot).toHaveBeenLastCalledWith(created.id));
fireEvent.click(screen.getByRole('button', { name: /^External scope test/u }));
openComposerOptions();
fireEvent.click(await screen.findByRole('button', { name: '选择知识库,本次已启用 2 个' }));
expect(screen.getByRole('checkbox', { name: /Existing/u })).toBeChecked();
expect(screen.getByRole('checkbox', { name: /Excluded/u })).not.toBeChecked();
expect(screen.getByRole('checkbox', { name: /Remote handbook.*Dify.*Company Dify/u })).toBeChecked();
});
}, 15_000);
it("ignores stale Git changes after switching projects", async () => {
const secondProject = {
@@ -13882,17 +13886,15 @@ describe("App", () => {
expect(screen.queryByDisplayValue('Unsaved navigation title')).not.toBeInTheDocument()
expect(screen.queryByText('Unsaved body')).not.toBeInTheDocument()
expect(api.magicNotes.update).not.toHaveBeenCalled()
})
}, 15_000)
it('keeps fixed apps available when application settings cannot be loaded', async () => {
it('keeps Knowledge available and Supervisor disabled when application settings cannot be loaded', async () => {
vi.mocked(api.updates!.getSettings).mockRejectedValue(new Error('unavailable'))
render(<App />)
fireEvent.click(screen.getByRole('button', { name: '知识库' }))
expect(await screen.findByLabelText('知识工作区')).toBeInTheDocument()
expect(screen.queryByRole('button', { name: '应用设置' })).not.toBeInTheDocument()
fireEvent.click(screen.getByRole('button', { name: '监督者' }))
expect(await screen.findByRole('heading', { name: '监督者' })).toBeInTheDocument()
expect(screen.queryByRole('button', { name: '应用设置' })).not.toBeInTheDocument()
expect(screen.queryByRole('button', { name: '监督者' })).not.toBeInTheDocument()
expect(api.heartbeats.update).not.toHaveBeenCalled()
})