Skip to content

fix(web): preserve workspace automation output summary on orchestrator finish#1122

Merged
cungminh2710 merged 4 commits into
mainfrom
cursor/critical-bug-investigation-6926
Jun 24, 2026
Merged

fix(web): preserve workspace automation output summary on orchestrator finish#1122
cungminh2710 merged 4 commits into
mainfrom
cursor/critical-bug-investigation-6926

Conversation

@cursor

@cursor cursor Bot commented Jun 24, 2026

Copy link
Copy Markdown
Contributor

Bug and impact

Workspace automations that run Contentful translation could write duplicate drafts to Contentful when the workflow step retried after a successful translation.

The orchestrator captured run.outputSummary at the start of execution. Mid-run tools (run_contentful_translation, create_translation_jobs) persisted fields like contentfulTranslationRunId to the database, but the orchestrator's final update spread the stale snapshot and erased those fields. On step retry, the Contentful tool could not find the prior translation run and created a new one, re-executing the writeback.

Root cause

  • run-workspace-orchestrator.ts finalized runs with { ...run.outputSummary, orchestratorStepResults }, overwriting tool-written summary fields.
  • run_contentful_translation only checked the top-level contentfulTranslationRunId when deciding whether to reuse an existing Contentful translation run.

Fix

  • Add buildWorkspaceOrchestratorOutputSummary() to merge tool-written fields (contentfulTranslationRunId, createTranslationJobs) from step results (and prior step results) into the final outputSummary.
  • Add resolveExistingContentfulTranslationRunId() with DB fallback by workspaceAutomationRunId, and skip re-execution when the linked Contentful run already succeeded.
  • Add retry idempotency for create_translation_jobs when createTranslationJobs / step results already exist.

Validation

  • vp test on new/updated workspace orchestrator and Contentful tool tests (14 tests)
  • vp check --fix
Open in Web View Automation 

@vercel

vercel Bot commented Jun 24, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
hyperlocalise Ignored Ignored Jun 24, 2026 10:38pm

Request Review

@cungminh2710
cungminh2710 marked this pull request as ready for review June 24, 2026 22:27
@greptile-apps

greptile-apps Bot commented Jun 24, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes a duplicate-draft bug in workspace automations by ensuring that tool-persisted fields (contentfulTranslationRunId, createTranslationJobs) are never silently erased when the orchestrator writes its final output summary. It also adds DB-backed idempotency so a retried step skips re-executing if the linked Contentful run already succeeded.

  • workspace-orchestrator-output-summary.ts (new): buildWorkspaceOrchestratorOutputSummary merges tool-written fields from in-memory step results and prior orchestratorStepResults before spreading them into the persisted output; mergeToolOutputSummaryIntoSessionRun keeps session.run.outputSummary up-to-date in-memory so both the success path and catch block see the live state.
  • resolve-existing-contentful-translation-run.ts (new): four-source lookup (top-level field → current step results → prior orchestratorStepResults → DB) ensures a previously created Contentful run is always found on retry; loadCompletedContentfulTranslationRunSummary gates the early-return on a succeeded status so in-progress or failed runs still proceed normally.
  • run_contentful_translation.ts / create_translation_jobs.ts: both tools call mergeToolOutputSummaryIntoSessionRun immediately after creating their primary record so the ID is durable before any subsequent async call can throw.

Confidence Score: 5/5

Safe to merge — the fix is well-scoped, all three mutation sites are covered, and the retry paths are backed by tests.

The root cause (stale snapshot overwriting tool-persisted fields) is cleanly addressed: mergeToolOutputSummaryIntoSessionRun is called immediately after each record creation, both the success and catch paths now use the live session.run.outputSummary, and buildWorkspaceOrchestratorOutputSummary performs a three-source merge so no previously written field is silently dropped. The new DB-backed idempotency guard prevents duplicate Contentful run creation on retry. Test coverage is added for the critical retry path, the DB fallback lookup, and the output-summary merge logic.

No files require special attention.

Important Files Changed

Filename Overview
apps/hyperlocalise-web/src/agents/automations/workspace/agent/run-workspace-orchestrator.ts Replaces inline spread with buildWorkspaceOrchestratorOutputSummary on both the success and catch paths, using session.run.outputSummary (live in-memory copy) instead of the stale startup snapshot.
apps/hyperlocalise-web/src/agents/automations/workspace/agent/workspace-orchestrator-output-summary.ts New module: merges tool-persisted fields (contentfulTranslationRunId, createTranslationJobs) from step results and prior orchestratorStepResults into the final output summary; exports mergeToolOutputSummaryIntoSessionRun for in-memory patching.
apps/hyperlocalise-web/src/agents/automations/workspace/agent/tools/resolve-existing-contentful-translation-run.ts New module: four-source lookup for an existing Contentful translation run ID (outputSummary, current stepResults, prior orchestratorStepResults, DB), plus loadCompletedContentfulTranslationRunSummary for skip-re-execution logic.
apps/hyperlocalise-web/src/agents/automations/workspace/agent/tools/run_contentful_translation.ts Adds idempotency guard: resolves any existing Contentful run ID, returns the completed summary without re-running the agent; calls mergeToolOutputSummaryIntoSessionRun immediately after creating a new Contentful run so the ID is durable before the agent executes.
apps/hyperlocalise-web/src/agents/automations/workspace/agent/tools/create_translation_jobs.ts Adds idempotency early-return using readCreateTranslationJobs; calls mergeToolOutputSummaryIntoSessionRun after job creation to keep the in-memory snapshot consistent.
apps/hyperlocalise-web/src/agents/automations/workspace/agent/workspace-orchestrator-output-summary.test.ts Three focused unit tests covering current step results, prior orchestratorStepResults fallback, and top-level field preservation for buildWorkspaceOrchestratorOutputSummary.
apps/hyperlocalise-web/src/agents/automations/workspace/agent/tools/resolve-existing-contentful-translation-run.test.ts New test file: covers in-memory resolution from prior orchestratorStepResults and the DB fallback path for resolveExistingContentfulTranslationRunId.
apps/hyperlocalise-web/src/agents/automations/workspace/agent/tools/run_contentful_translation.test.ts Adds DB mock infrastructure and a regression test for the completed-run early-return path; all pre-existing tests are guarded with mockEmptyContentfulRunLookup.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant Orch as Orchestrator
    participant Tool as run_contentful_translation
    participant Resolve as resolveExistingContentfulTranslationRunId
    participant DB as Database
    participant Agent as runContentfulAgent

    Orch->>Tool: execute()
    Tool->>Resolve: resolveExistingContentfulTranslationRunId(session)
    Resolve->>Resolve: check outputSummary.contentfulTranslationRunId
    Resolve->>Resolve: check session.stepResults
    Resolve->>Resolve: check outputSummary.orchestratorStepResults
    alt not found in memory
        Resolve->>DB: "SELECT id FROM contentfulTranslationRuns WHERE workspaceAutomationRunId=..."
        DB-->>Resolve: existingRunId (or null)
    end
    Resolve-->>Tool: existingRunId

    alt existingRunId found
        Tool->>DB: loadCompletedContentfulTranslationRunSummary(existingRunId)
        DB-->>Tool: completedSummary (or null if running/failed)
        alt completedSummary present (succeeded)
            Tool->>Orch: mergeToolOutputSummaryIntoSessionRun + return summary (SKIP re-run)
        else run not yet complete
            Tool->>Agent: runContentfulAgent(existingRunId)
        end
    else no existingRunId
        Tool->>DB: createContentfulTranslationRun(...)
        DB-->>Tool: translationRun.id
        Tool->>DB: updateWorkspaceAutomationRun (persist contentfulTranslationRunId)
        Tool->>Orch: mergeToolOutputSummaryIntoSessionRun (in-memory update)
        Tool->>Agent: runContentfulAgent(translationRun.id)
    end

    Agent-->>Tool: result
    Tool-->>Orch: stepResult

    Orch->>Orch: buildWorkspaceOrchestratorOutputSummary(session.run.outputSummary, session.stepResults)
    Orch->>DB: updateWorkspaceAutomationRun (final output)
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant Orch as Orchestrator
    participant Tool as run_contentful_translation
    participant Resolve as resolveExistingContentfulTranslationRunId
    participant DB as Database
    participant Agent as runContentfulAgent

    Orch->>Tool: execute()
    Tool->>Resolve: resolveExistingContentfulTranslationRunId(session)
    Resolve->>Resolve: check outputSummary.contentfulTranslationRunId
    Resolve->>Resolve: check session.stepResults
    Resolve->>Resolve: check outputSummary.orchestratorStepResults
    alt not found in memory
        Resolve->>DB: "SELECT id FROM contentfulTranslationRuns WHERE workspaceAutomationRunId=..."
        DB-->>Resolve: existingRunId (or null)
    end
    Resolve-->>Tool: existingRunId

    alt existingRunId found
        Tool->>DB: loadCompletedContentfulTranslationRunSummary(existingRunId)
        DB-->>Tool: completedSummary (or null if running/failed)
        alt completedSummary present (succeeded)
            Tool->>Orch: mergeToolOutputSummaryIntoSessionRun + return summary (SKIP re-run)
        else run not yet complete
            Tool->>Agent: runContentfulAgent(existingRunId)
        end
    else no existingRunId
        Tool->>DB: createContentfulTranslationRun(...)
        DB-->>Tool: translationRun.id
        Tool->>DB: updateWorkspaceAutomationRun (persist contentfulTranslationRunId)
        Tool->>Orch: mergeToolOutputSummaryIntoSessionRun (in-memory update)
        Tool->>Agent: runContentfulAgent(translationRun.id)
    end

    Agent-->>Tool: result
    Tool-->>Orch: stepResult

    Orch->>Orch: buildWorkspaceOrchestratorOutputSummary(session.run.outputSummary, session.stepResults)
    Orch->>DB: updateWorkspaceAutomationRun (final output)
Loading

Reviews (2): Last reviewed commit: "fix(web): address orchestrator output su..." | Re-trigger Greptile

@cungminh2710

Copy link
Copy Markdown
Contributor

@cursoragent rebase main, fix conflicts and Fix the following 3 code review issues. Work through them one at a time, proposing concise fixes.


Issue 1 of 3

apps/hyperlocalise-web/src/agents/automations/workspace/agent/run-workspace-orchestrator.ts:280-283
Catch block uses stale run.outputSummary, undoing the fix on the error path

The catch block calls buildWorkspaceOrchestratorOutputSummary(run.outputSummary, session.stepResults), where run is the snapshot loaded at orchestrator startup. During execution, mergeToolOutputSummaryIntoSessionRun updates session.run.outputSummary in memory (e.g., run_contentful_translation writes contentfulTranslationRunId there before calling runContentfulAgent). If an unhandled exception fires after that write but before session.stepResults.run_contentful_translation is populated, neither the stale run.outputSummary nor the incomplete session.stepResults contains contentfulTranslationRunId. The catch block's updateWorkspaceAutomationRun call then overwrites the previously persisted run ID with null — exactly the data-loss scenario this PR was written to prevent. The fix is to use session.run.outputSummary as the base in both the catch block and, for consistency, the success path at line 238.

Issue 2 of 3

apps/hyperlocalise-web/src/agents/automations/workspace/agent/tools/create_translation_jobs.ts:10-50
readCreateTranslationJobsOutput replicates the three-source lookup from readCreateTranslationJobs in workspace-orchestrator-output-summary.ts but uses typeof x.jobId === "string", which accepts an empty string, while the shared helper uses readString(x.jobId) which additionally requires a non-empty value. Importing the shared helper removes the duplication and the inconsistency.

import type { WorkspaceOrchestratorSession } from "../context";
import {
  mergeToolOutputSummaryIntoSessionRun,
  readCreateTranslationJobs,
} from "../workspace-orchestrator-output-summary";

const jobQueue = createTranslationJobEventQueue();

Issue 3 of 3

apps/hyperlocalise-web/src/agents/automations/workspace/agent/tools/run_contentful_translation.test.ts:147-157
Critical early-return path has no test coverage

The mocks for runContentfulAgent, createContentfulTranslationRun, and updateWorkspaceAutomationRun were added, and session() now accepts outputSummary and stepResults, but no test actually exercises the new execute logic. Specifically, the retry-idempotency path — where resolveExistingContentfulTranslationRunId finds an ID and loadCompletedContentfulTranslationRunSummary returns a completed summary — is entirely uncovered.

@cursor

cursor Bot commented Jun 24, 2026

Copy link
Copy Markdown
Contributor Author

Rebased onto main, resolved conflicts, and addressed all three review items.

Rebase

Rebased cursor/critical-bug-investigation-6926 onto main and merged conflicts in run_contentful_translation.test.ts, keeping main’s execute-path tests and the PR’s idempotency coverage.

Issue 1 — stale run.outputSummary on error path

Both success and catch paths in run-workspace-orchestrator.ts now use session.run.outputSummary as the merge base, so mid-run tool patches (e.g. contentfulTranslationRunId) survive orchestrator failures.

Issue 2 — duplicated readCreateTranslationJobsOutput

Removed the local duplicate in create_translation_jobs.ts and imported the shared readCreateTranslationJobs helper (exported from workspace-orchestrator-output-summary.ts), which enforces non-empty jobId via readString.

Issue 3 — missing early-return test coverage

Added returns the completed summary without re-running translation on retry, which verifies that when an existing completed Contentful run is found, createContentfulTranslationRun and runContentfulAgent are not called and the cached summary is returned.

Validation

  • vp test — 13 tests passed across the affected files
  • vp check --fix — passed

Changes are pushed to cursor/critical-bug-investigation-6926.

Open in Web Open in Cursor 

cursoragent and others added 4 commits June 24, 2026 22:36
…r finish

Prevent the orchestrator from overwriting tool-written outputSummary fields
with the stale run snapshot. Reuse completed Contentful translation runs on
workflow step retries instead of creating duplicate writebacks.

Co-authored-by: Minh Cung <cungminh2710@users.noreply.github.com>
Co-authored-by: Minh Cung <cungminh2710@users.noreply.github.com>
Co-authored-by: Minh Cung <cungminh2710@users.noreply.github.com>
Use session.run.outputSummary as the merge base on both success and
error paths so mid-run tool patches are not erased on failure.

Reuse readCreateTranslationJobs in create_translation_jobs for consistent
non-empty jobId validation, and add execute-path coverage for the
Contentful translation retry idempotency early return.

Co-authored-by: Minh Cung <cungminh2710@users.noreply.github.com>
@cursor
cursor Bot force-pushed the cursor/critical-bug-investigation-6926 branch from 9d066d0 to 86763b2 Compare June 24, 2026 22:38
@cungminh2710
cungminh2710 merged commit f7b5486 into main Jun 24, 2026
9 checks passed
@cungminh2710
cungminh2710 deleted the cursor/critical-bug-investigation-6926 branch June 24, 2026 22:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants