- Notifications
You must be signed in to change notification settings - Fork 0
revert: fix: close bug-hunter stale-state paths#142
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Uh oh!
There was an error while loading. Please reload this page.
Changes from all commits
File filter
Filter by extension
Conversations
Uh oh!
There was an error while loading. Please reload this page.
Jump to
Uh oh!
There was an error while loading. Please reload this page.
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -91,7 +91,7 @@ export async function POST(request: Request) { | ||
| const documentIds = Array.from(new Set(parsed.documentIds)); | ||
| const { data: documents, error: documentError } = await supabase | ||
| .from("documents") | ||
| .select("id,owner_id,title,file_name,source_path,import_batch_id,status,error_message,page_count,chunk_count,image_count,metadata") | ||
| .select("id,owner_id,title,file_name,source_path,import_batch_id,status,metadata") | ||
| .eq("owner_id", user.id) | ||
| .in("id", documentIds); | ||
| if (documentError) throw new Error(documentError.message); | ||
| @@ -166,15 +166,6 @@ export async function POST(request: Request) { | ||
| } | ||
| const atomicReindex = isAtomicReindexCandidate(document); | ||
| const rollbackDocumentPayload = atomicReindex | ||
| ? { error_message: document.error_message ?? null } | ||
| : { | ||
| status: document.status ?? null, | ||
| error_message: document.error_message ?? null, | ||
| page_count: document.page_count ?? 0, | ||
| chunk_count: document.chunk_count ?? 0, | ||
| image_count: document.image_count ?? 0, | ||
| }; | ||
| const { error: updateError } = await supabase | ||
| .from("documents") | ||
| .update( | ||
| @@ -197,17 +188,7 @@ export async function POST(request: Request) { | ||
| }) | ||
| .select("id") | ||
| .single(); | ||
| if (jobError) { | ||
| const { error: rollbackError } = await supabase | ||
| .from("documents") | ||
| .update(rollbackDocumentPayload) | ||
| .eq("id", document.id) | ||
| .eq("owner_id", user.id); | ||
| if (rollbackError) { | ||
| throw new Error(`Failed to enqueue bulk reindex job: ${jobError.message}; rollback failed: ${rollbackError.message}`); | ||
| } | ||
| throw new Error(jobError.message); | ||
| } | ||
| if (jobError) throw new Error(jobError.message); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
In the bulk full/retry path, each document is mutated to queued and its counts are zeroed before inserting the ingestion job; if this insert fails, the per-document result reports failure but the document row is left in the queued/empty state with no pending job. This makes bulk reindex failures leave selected documents stuck as active work that no worker can process. Useful? React with 👍 / 👎. | ||
| results.push({ documentId: document.id, mode: parsed.mode, ok: true, jobId: job.id }); | ||
| } catch (error) { | ||
| results.push({ | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -23,9 +23,7 @@ export async function POST(request: Request, { params }: { params: Promise<{ id: | ||
| const { data: job, error: jobError } = await supabase | ||
| .from("ingestion_jobs") | ||
| .select( | ||
| "id,document_id,batch_id,status,stage,progress,error_message,attempt_count,max_attempts,locked_at,locked_by,next_run_at,completed_at,documents!inner(owner_id)", | ||
| ) | ||
| .select("id,document_id,batch_id,status,locked_at,documents!inner(owner_id)") | ||
| .eq("id", id) | ||
| .eq("documents.owner_id", user.id) | ||
| .maybeSingle(); | ||
| @@ -83,27 +81,7 @@ export async function POST(request: Request, { params }: { params: Promise<{ id: | ||
| .update({ status: "queued", error_message: null }) | ||
| .eq("id", job.document_id) | ||
| .eq("owner_id", user.id); | ||
| if (documentError) { | ||
| const { error: rollbackError } = await supabase | ||
| .from("ingestion_jobs") | ||
| .update({ | ||
| status: job.status, | ||
| stage: job.stage, | ||
| progress: job.progress, | ||
| error_message: job.error_message, | ||
| attempt_count: job.attempt_count, | ||
| max_attempts: job.max_attempts, | ||
| locked_at: job.locked_at, | ||
| locked_by: job.locked_by, | ||
| next_run_at: job.next_run_at, | ||
| completed_at: job.completed_at, | ||
| }) | ||
| .eq("id", id); | ||
| if (rollbackError) { | ||
| throw new Error(`${documentError.message}; failed to roll back retried job state: ${rollbackError.message}`); | ||
| } | ||
| throw new Error(documentError.message); | ||
| } | ||
| if (documentError) throw new Error(documentError.message); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
If the guarded job update succeeds but the following document update returns an error, the API now reports failure while leaving the ingestion job reset to Useful? React with 👍 / 👎. | ||
| return NextResponse.json({ job: data }); | ||
| } catch (error) { | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -24,8 +24,6 @@ const uploadMetadataSchema = z | ||
| export async function POST(request: Request) { | ||
| let supabase: ReturnType<typeof createAdminClient> | null = null; | ||
| let uploadedPath: string | null = null; | ||
| let insertedDocumentId: string | null = null; | ||
| let insertedDocumentOwnerId: string | null = null; | ||
| try { | ||
| supabase = createAdminClient(); | ||
| @@ -133,8 +131,6 @@ export async function POST(request: Request) { | ||
| .single(); | ||
| if (documentError) throw new Error(documentError.message); | ||
| insertedDocumentId = documentId; | ||
| insertedDocumentOwnerId = user.id; | ||
| const { data: job, error: jobError } = await supabase | ||
| .from("ingestion_jobs") | ||
| @@ -149,19 +145,7 @@ export async function POST(request: Request) { | ||
| .select() | ||
| .single(); | ||
| if (jobError) { | ||
| const { error: rollbackDocumentError } = await supabase | ||
| .from("documents") | ||
| .delete() | ||
| .eq("id", documentId) | ||
| .eq("owner_id", user.id); | ||
| if (rollbackDocumentError) { | ||
| throw new Error(`Failed to enqueue ingestion job: ${jobError.message}; rollback failed: ${rollbackDocumentError.message}`); | ||
| } | ||
| insertedDocumentId = null; | ||
| insertedDocumentOwnerId = null; | ||
| throw new Error(jobError.message); | ||
| } | ||
| if (jobError) throw new Error(jobError.message); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When the storage upload and Useful? React with 👍 / 👎. | ||
| await writeAuditLog(supabase, { | ||
| ownerId: user.id, | ||
| @@ -173,18 +157,6 @@ export async function POST(request: Request) { | ||
| return NextResponse.json({ document, job }, { status: 201 }); | ||
| } catch (error) { | ||
| if (insertedDocumentId && insertedDocumentOwnerId && supabase) { | ||
| try { | ||
| await supabase.from("documents").delete().eq("id", insertedDocumentId).eq("owner_id", insertedDocumentOwnerId); | ||
| } catch (cleanupError) { | ||
| logger.error("Upload cleanup failed; document row may be orphaned", { | ||
| documentId: insertedDocumentId, | ||
| ownerId: insertedDocumentOwnerId, | ||
| message: cleanupError instanceof Error ? cleanupError.message : String(cleanupError), | ||
| }); | ||
| } | ||
| } | ||
| if (uploadedPath && supabase) { | ||
| try { | ||
| await supabase.storage.from(env.SUPABASE_DOCUMENT_BUCKET).remove([uploadedPath]); | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -69,12 +69,8 @@ function readSavedForms() { | ||
| async function copyText(value: string) { | ||
| if (navigator.clipboard?.writeText) { | ||
| try { | ||
| await navigator.clipboard.writeText(value); | ||
| return; | ||
| } catch { | ||
| // Fall through to the legacy selection path for restricted browser contexts. | ||
| } | ||
| await navigator.clipboard.writeText(value); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
In browsers or embedded contexts where Useful? React with 👍 / 👎. | ||
| return; | ||
| } | ||
| const textArea = document.createElement("textarea"); | ||
| @@ -84,12 +80,9 @@ async function copyText(value: string) { | ||
| textArea.style.opacity = "0"; | ||
| document.body.appendChild(textArea); | ||
| textArea.select(); | ||
| try { | ||
| const copied = document.execCommand?.("copy"); | ||
| if (copied === false) throw new Error("copy command rejected"); | ||
| } finally { | ||
| document.body.removeChild(textArea); | ||
| } | ||
| const copied = document.execCommand?.("copy"); | ||
| document.body.removeChild(textArea); | ||
| if (copied === false) throw new Error("copy command rejected"); | ||
| } | ||
| function chipToneClass(tone: ServiceChipTone | null | undefined) { | ||
This file was deleted.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For non-indexed full reindexes, the document is first changed to
status: "queued"with page/chunk/image counts set to zero, but if the subsequent job insert fails this line returns an error with no job queued and no rollback. That leaves the document looking actively queued and emptied even though no worker can pick it up, so the previous indexed/failed state is lost until someone repairs the row manually.Useful? React with 👍 / 👎.