From 6ce61eae594565bec72101c6d676014802ddd1ba Mon Sep 17 00:00:00 2001 From: Xiyuan Chen <52963600+GareArc@users.noreply.github.com> Date: Mon, 1 Jun 2026 22:20:33 -0700 Subject: [PATCH] fix(cli): invalidate app metadata cache on 422 to clear stale data (#36921) --- cli/src/commands/run/app/run.test.ts | 20 ++++++++++++++++++++ cli/src/commands/run/app/run.ts | 19 +++++++++++++++++++ cli/test/fixtures/dify-mock/scenarios.ts | 1 + cli/test/fixtures/dify-mock/server.ts | 6 ++++++ 4 files changed, 46 insertions(+) diff --git a/cli/src/commands/run/app/run.test.ts b/cli/src/commands/run/app/run.test.ts index efe6a07e80..d0d4586577 100644 --- a/cli/src/commands/run/app/run.test.ts +++ b/cli/src/commands/run/app/run.test.ts @@ -351,6 +351,26 @@ describe('runApp', () => { }) }) + it('422 on run: invalidates cache and adds republish hint', async () => { + const io = bufferStreams() + const cache = await loadAppInfoCache({ store: getCache(CACHE_APP_INFO) }) + // warm cache with successful run + await runApp( + { appId: 'app-1', message: 'hi' }, + { bundle: bundle(), http: testHttpClient(mock.url, 'dfoa_test'), host: mock.url, io, cache }, + ) + expect(cache.get(mock.url, 'app-1')).toBeDefined() + + mock.setScenario('run-422-stale') + const err = await runApp( + { appId: 'app-1', message: 'hi' }, + { bundle: bundle(), http: testHttpClient(mock.url, { bearer: 'dfoa_test', retryAttempts: 0 }), host: mock.url, io, cache }, + ).catch((e: unknown) => e) + expect(err).toMatchObject({ code: 'server_4xx_other', httpStatus: 422 }) + expect((err as { hint?: string }).hint).toMatch(/cache cleared/) + expect(cache.get(mock.url, 'app-1')).toBeUndefined() + }) + it('workflow: --file overrides same-named key from --inputs (file wins)', async () => { const io = bufferStreams() const cache = await loadAppInfoCache({ store: getCache(CACHE_APP_INFO) }) diff --git a/cli/src/commands/run/app/run.ts b/cli/src/commands/run/app/run.ts index 64bee92f3b..6dae0e4143 100644 --- a/cli/src/commands/run/app/run.ts +++ b/cli/src/commands/run/app/run.ts @@ -82,6 +82,25 @@ export async function runApp(opts: RunAppOptions, deps: RunAppDeps): Promise { const m = await meta.get(opts.appId, wsId, [FieldInfo]) const mode = m.info?.mode ?? '' if (mode === '') diff --git a/cli/test/fixtures/dify-mock/scenarios.ts b/cli/test/fixtures/dify-mock/scenarios.ts index cfd55f438c..94bfd1b5c7 100644 --- a/cli/test/fixtures/dify-mock/scenarios.ts +++ b/cli/test/fixtures/dify-mock/scenarios.ts @@ -13,6 +13,7 @@ export type Scenario | 'hitl-resume' | 'server-version-empty' | 'server-version-unsupported' + | 'run-422-stale' export type AccountFixture = { id: string diff --git a/cli/test/fixtures/dify-mock/server.ts b/cli/test/fixtures/dify-mock/server.ts index 59f318e092..af04508763 100644 --- a/cli/test/fixtures/dify-mock/server.ts +++ b/cli/test/fixtures/dify-mock/server.ts @@ -288,6 +288,12 @@ export function buildApp(getScenario: () => Scenario, state?: MockState): Hono { const isAgent = app.is_agent === true || app.mode === 'agent-chat' const query = body.query ?? '' const scenario = getScenario() + if (scenario === 'run-422-stale') { + return c.json( + { error: { code: 'query_not_supported_for_workflow', message: 'query not supported for workflow mode' } }, + { status: 422 }, + ) + } if (scenario === 'stream-error') { const errSse = sseChunks([{ event: 'error', data: { message: 'boom', status: 503 } }]) return new Response(errSse, { status: 200, headers: { 'content-type': 'text/event-stream' } })