Heal zombie running projects stuck on Review forever.
If the event log already ends with pipeline_complete, flip meta to complete on project/status fetch so Progress no longer spins on a dead worker. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -2,6 +2,14 @@
|
||||
|
||||
What's new in Pinscope.
|
||||
|
||||
## 2.28.4 — 2026-09-13 — Unstick zombie running pipelines
|
||||
|
||||
A finished run whose worker died before flipping meta stayed `running`, so Progress showed Review spinning forever. Heal those projects when the event log already ends with `pipeline_complete`.
|
||||
|
||||
- [Fixed] `heal_if_pipeline_finished` on project get / pipeline status.
|
||||
- [Fixed] Progress stepper marks all stages complete on `pipeline_complete`.
|
||||
- [Fixed] Admin sweeper also covers local zombies without `execution_name`.
|
||||
|
||||
## 2.28.3 — 2026-09-13 — Stop progress→report bounce on finished projects
|
||||
|
||||
Opening a finished project no longer flashes Processing and dumps you on the report. Progress only auto-opens the report after a live run on that visit.
|
||||
|
||||
@@ -20,7 +20,7 @@ import {
|
||||
import { PipelineStepper } from "@/components/progress/pipeline-stepper";
|
||||
import { PausedRunBanner } from "@/components/billing/paused-run-banner";
|
||||
import { usePipelineProgress } from "@/hooks/use-pipeline-progress";
|
||||
import { cancelPipeline, fetchProject, fetchReport, resumePipeline, reprocessPipeline } from "@/lib/api";
|
||||
import { cancelPipeline, fetchPipelineStatus, fetchProject, fetchReport, resumePipeline, reprocessPipeline } from "@/lib/api";
|
||||
import type { PauseCheckpoint } from "@/lib/types";
|
||||
import {
|
||||
AlertTriangle,
|
||||
@@ -72,32 +72,41 @@ export default function ProgressPage({
|
||||
}
|
||||
|
||||
// Decide whether this visit is a live run before opening SSE / auto-redirect.
|
||||
// /status also heals zombies whose event log already ends with pipeline_complete.
|
||||
useEffect(() => {
|
||||
let cancelledFetch = false;
|
||||
fetchProject(id)
|
||||
.then((p) => {
|
||||
(async () => {
|
||||
try {
|
||||
const st = await fetchPipelineStatus(id);
|
||||
if (cancelledFetch) return;
|
||||
setProjectName(p.name);
|
||||
const nameP = fetchProject(id).then((p) => {
|
||||
if (!cancelledFetch) {
|
||||
setProjectName(p.name);
|
||||
if (p.pauseCheckpoint) setProjectCheckpoint(p.pauseCheckpoint);
|
||||
}
|
||||
}).catch(() => {});
|
||||
|
||||
if (
|
||||
p.status === "paused_insufficient_credits" ||
|
||||
p.status === "paused_by_user"
|
||||
st.status === "paused_insufficient_credits" ||
|
||||
st.status === "paused_by_user"
|
||||
) {
|
||||
setProjectPaused(true);
|
||||
setProjectCheckpoint(p.pauseCheckpoint ?? null);
|
||||
setGate("paused");
|
||||
await nameP;
|
||||
return;
|
||||
}
|
||||
if (p.status === "running" || p.status === "queued") {
|
||||
if (st.status === "running" || st.status === "queued") {
|
||||
setGate("live");
|
||||
await nameP;
|
||||
return;
|
||||
}
|
||||
// Finished / draft — progress is the wrong page; go to the project hub.
|
||||
// Finished / draft / healed — progress is the wrong page.
|
||||
setGate("idle");
|
||||
router.replace(`/project/${id}`);
|
||||
})
|
||||
.catch(() => {
|
||||
} catch {
|
||||
if (!cancelledFetch) setGate("live");
|
||||
});
|
||||
}
|
||||
})();
|
||||
return () => {
|
||||
cancelledFetch = true;
|
||||
};
|
||||
|
||||
@@ -99,6 +99,15 @@ export function usePipelineProgress(projectId: string | null) {
|
||||
|
||||
if (eventType === "pipeline_complete" || event.lastEventId === "pipeline_complete") {
|
||||
setSummary(data.summary as Record<string, number>);
|
||||
// Force every stage complete so a historical replay cannot leave
|
||||
// "Review Design" spinning if the last substep was still running.
|
||||
setSteps((prev) =>
|
||||
prev.map((s) => ({
|
||||
...s,
|
||||
status: "complete" as const,
|
||||
substeps: s.substeps.map((ss) => ({ ...ss, status: "complete" as const })),
|
||||
})),
|
||||
);
|
||||
setDone(true);
|
||||
terminalRef.current = true;
|
||||
esRef.current?.close();
|
||||
|
||||
Reference in New Issue
Block a user