fix(cli): invalidate app metadata cache on 422 to clear stale data (#36921)

This commit is contained in:
Xiyuan Chen 2026-06-01 22:20:33 -07:00 committed by GitHub
parent 079af312c6
commit 6ce61eae59
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 46 additions and 0 deletions

View File

@ -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) })

View File

@ -82,6 +82,25 @@ export async function runApp(opts: RunAppOptions, deps: RunAppDeps): Promise<voi
const wsId = resolveWorkspaceId({ flag: opts.workspace, env: env('DIFY_WORKSPACE_ID'), active: deps.active })
const apps = new AppsClient(deps.http)
const meta = new AppMetaClient({ apps, host: deps.host, cache: deps.cache })
try {
await executeRun(opts, deps, meta, wsId)
}
catch (err) {
if (err instanceof BaseError && err.httpStatus === 422) {
await meta.invalidate(opts.appId)
throw err.withHint('app metadata cache cleared — if the app was recently republished, run the command again')
}
throw err
}
}
async function executeRun(
opts: RunAppOptions,
deps: RunAppDeps,
meta: AppMetaClient,
wsId: string,
): Promise<void> {
const m = await meta.get(opts.appId, wsId, [FieldInfo])
const mode = m.info?.mode ?? ''
if (mode === '')

View File

@ -13,6 +13,7 @@ export type Scenario
| 'hitl-resume'
| 'server-version-empty'
| 'server-version-unsupported'
| 'run-422-stale'
export type AccountFixture = {
id: string

View File

@ -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' } })