Skip to content

Commit bbafe1d

Browse files
snowopsdevcodex
andcommitted
fix(pipeline): report progress only after article writes succeed
Co-Authored-By: Codex <noreply@openai.com>
1 parent 56c0731 commit bbafe1d

2 files changed

Lines changed: 43 additions & 8 deletions

File tree

pipeline/src/stages.ts

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -162,15 +162,7 @@ export async function runPipeline(
162162
// and a scheduled run's alerting sees stuck articles.
163163
try {
164164
const outcome = await stage.run(article, ctx)
165-
processed.add(article.id)
166-
finalStatusByArticle.set(article.id, outcome.status)
167165
const warnings = outcome.warnings ?? []
168-
if (warnings.length > 0) {
169-
warned += 1
170-
for (const warning of warnings) {
171-
console.warn(`[${stage.name}] article ${article.id} warning: ${warning}`)
172-
}
173-
}
174166
await ctx.payload.update({
175167
collection: 'articles',
176168
id: article.id,
@@ -192,6 +184,16 @@ export async function runPipeline(
192184
},
193185
},
194186
})
187+
// A stage outcome is only progress once the article write succeeds.
188+
// Keep the last persisted status when a later stage fails to save.
189+
processed.add(article.id)
190+
finalStatusByArticle.set(article.id, outcome.status)
191+
if (warnings.length > 0) {
192+
warned += 1
193+
for (const warning of warnings) {
194+
console.warn(`[${stage.name}] article ${article.id} warning: ${warning}`)
195+
}
196+
}
195197
console.log(
196198
`[${stage.name}] article ${article.id} "${article.keyword}" -> ${outcome.status}`,
197199
)

pipeline/test/runPipeline.test.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,39 @@ const quietly = async <T>(body: () => Promise<T>): Promise<T> => {
8585
}
8686

8787
describe('runPipeline', () => {
88+
it('reports only persisted progress when an article write fails', async () => {
89+
const { ctx } = fakeCtx([articleAt(1, 'topic_selected')])
90+
ctx.payload.update = async () => {
91+
throw new Error('database write failed')
92+
}
93+
const summary = await quietly(() => runPipeline(ctx, {
94+
stages: [stubStage('research', 'topic_selected', 'researched')],
95+
}))
96+
assert.equal(summary.failed, 1)
97+
assert.deepEqual(summary.articleIds, [])
98+
assert.deepEqual(summary.finalStatuses, {})
99+
assert.equal(summary.failures[0]?.message, 'database write failed')
100+
})
101+
102+
it('keeps the last persisted status when a later stage write fails', async () => {
103+
const article = articleAt(1, 'topic_selected')
104+
const { ctx } = fakeCtx([article])
105+
ctx.payload.update = (async ({ data }: { data: { status: Article['status'] } }) => {
106+
if (data.status === 'drafted') throw new Error('database write failed')
107+
article.status = data.status
108+
return article
109+
}) as unknown as typeof ctx.payload.update
110+
const summary = await quietly(() => runPipeline(ctx, {
111+
stages: [
112+
stubStage('research', 'topic_selected', 'researched'),
113+
stubStage('generate', 'researched', 'drafted'),
114+
],
115+
}))
116+
assert.equal(summary.failed, 1)
117+
assert.deepEqual(summary.articleIds, [1])
118+
assert.deepEqual(summary.finalStatuses, { researched: 1 })
119+
})
120+
88121
it('reports no failures for a clean run and advances every article', async () => {
89122
const { ctx, updates } = fakeCtx([
90123
articleAt(1, 'topic_selected'),

0 commit comments

Comments
 (0)