Skip to content

Fix cross-project deploy: make the target match the local project - #1588

Open
midigofrank wants to merge 2 commits into
release/nextfrom
ofn-4761-cross-project-deploy
Open

midigofrank wants to merge 2 commits into
release/nextfrom
ofn-4761-cross-project-deploy

Conversation

@midigofrank

@midigofrank midigofrank commented Oct 15, 2026 •

Copy link
Copy Markdown
Contributor

Short Description

Deploying a workspace to a different project sent none of its workflows. A cross-project deploy now makes the target look exactly like the local project, including removing workflows that only exist on the target.

Fixes OFN-4761, OFN-4762

Implementation Details

  • Root cause: merge candidates came from findLocallyChangedWorkflows, which compares the workspace with its own forked_from.
    • An unchanged workspace of A had no candidates, so deploying it to B sent B's workflows back unchanged. Only a channel diff got past "Nothing to deploy".
    • This broke Lightning's GitHub -> OpenFn setup: the run was green, but nothing was deployed.
  • Mirror by default: when the target uuid differs from the local project's, every local workflow is a candidate, and so is every workflow on the target.
    • remoteProject.diff() reports target-only workflows as removed in workflowDiffs, and they are sent as delete: true.
    • Same-uuid deploys are unchanged.
  • Guard: if a cross-project deploy would remove workflows and there's no confirmation prompt (--no-confirm), it stops with REMOVES_WORKFLOWS unless --force is passed. --dry-run isn't blocked. Interactive runs show the removals in the plan and ask as usual.
  • The divergence error now says when the target is a different project. --force is still required in that case.
  • @openfn/project now exports Workflow.

QA Notes

  • New deploy tests:
    • An unchanged A → B leaves B with only A's workflows, with A's content.
    • --no-confirm without --force throws and leaves B untouched.
    • The cross-project divergence message, and --force.
  • The existing "deploy a file to a different target" test now passes --force, because that deploy removes the target's own workflows.
  • --workflow is unchanged: it force-deploys only the named workflows, which must exist locally. --workflow all is not in this PR (see review discussion).
  • Will conflict trivially with the OFN-4760 PR in deploy.ts.
  • Full-package ava runs segfault locally (also on the base commit); every test file passes when run on its own.

AI Usage

  • I have used Claude Code
  • I have used another model
  • I have not used AI

Deploying a workspace to a different project now treats every local
workflow as a merge candidate, instead of only those changed since the
workspace's own last sync. --remove-unmapped sends delete entries for
target workflows that don't exist locally.
@josephjclark
josephjclark force-pushed the ofn-4761-cross-project-deploy branch from c41f26d to 54032a0 Compare October 16, 2026 14:27

@josephjclark josephjclark left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I really think something is off here Frank and I'd like to see a rethink. See comments.

I welcome discussion abotu this - let's talk it through and then decide next steps

Comment thread .changeset/deploy-remove-unmapped.md Outdated
'@openfn/project': patch
---

Add `--remove-unmapped` to `project deploy` to delete target workflows that don't exist locally

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am pretty sure that --remove-unmapped needs to be true by default, and not exposed to the CLI.

I think the user can bass --workflows to get different behaviour. If a workflow is picked, the local state will apply to the remote (if the workflow does not exist locally, it is removed from the remote). We should support --workflows all. which means "make the target look like the source".

So what I'm looking for is: we have the correct default behaviour built in (dependong on whether the UUID matches), but users can override the behaviour with --workflows if they want to force-push a change

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed. Removed the flag and this changeset. Mirroring is now the default when the UUIDs differ.

Comment thread packages/cli/src/projects/deploy.ts Outdated
o2.workflow,
o2.credentials,
o2.checkout,
override(o2.removeUnmapped, {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not convinced this needs exposing

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Gone.

Comment thread packages/cli/src/projects/deploy.ts Outdated
const didChannelsChange = channelsChanged(localProject, remoteProject);
if (!workflowDiffs.length && !didCollectionsChange && !didChannelsChange) {
// workflows on the target which don't exist locally at all
const unmappedWorkflows = options.removeUnmapped

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hesitant on whether this belongs here. Is there a test that covers it?

I think it's saying: if there's an unmapped workflow that needs removing, then there's something to deploy. Why isn't that covered by the other diff stuff here?

I'd like it to be covered by workflowDiffs.

Worth noting tht adding this unmapped check alongside locally changed is adding quite a lot of complexity. I can hardly follow the code these days and that scares me. It was never meant to get so complicated.

@midigofrank midigofrank Oct 20, 2026 •

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed. When the target is a different project, its workflow ids are added to the merge candidates, so the existing diff reports them as removed and they flow through workflowDiffs like everything else. Covered by the "makes a different project look exactly like the local project" test.

t.falsy(logger._find('success', /Nothing to deploy/));
const added = findWorkflow(TWO_WORKFLOWS_UUID, 'My Workflow');
t.is(added.jobs['transform-data'].body, 'fn()');
// the target's other workflows are left alone

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this right?

Don't we want the remote to look exactly the same as the local in this case?

When posting to a different UUID, without any extra instruction, I think we're saying "take my local project and make the remote look exactly like it".

That means removing workflow A and B from the remote.

Now, in this PR right now I should be able to do that with --remove-unmapped. But I think we need to have the correct default behavioour first. I want to be really clear on this.

@midigofrank midigofrank Oct 20, 2026 •

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, that was asserting the wrong default. Flipped it: the target now ends up with only the local workflows. There's also a new test for the --no-confirm guard.

@github-project-automation github-project-automation Bot moved this from New Issues to In review in Core Oct 19, 2026
Drop --remove-unmapped: target-only workflows are removed by default when the
target UUID differs, and need --force when there is no confirmation prompt.
@midigofrank

midigofrank commented Oct 20, 2026 •

Copy link
Copy Markdown
Contributor Author

Thanks Joe, agreed. I've made changes:

  • Dropped --remove-unmapped entirely.
  • Deploying to a different project (UUID mismatch) now mirrors by default: the target's own workflows become candidates, so remoteProject.diff() reports them as removed in workflowDiffs and they're deleted. No separate unmapped check anymore.
  • Same-UUID deploys are unchanged.

One guard I added: mirroring means a cross-project deploy can delete workflows. If that's about to happen and there's no prompt (--no-confirm, i.e. CI), it refuses unless --force is passed. Interactively you still see the plan and confirm as before. A wrong UUID in CI shouldn't silently wipe a project. Happy to drop it if you disagree.

I've left --workflows all (and letting --workflow remove a workflow that's missing locally) out of this PR. Right now --workflow errors if an id doesn't exist locally. Happy to do it here or as a follow-up, your call.

@midigofrank midigofrank changed the title Fix cross-project deploy and add --remove-unmapped to project deploy Fix cross-project deploy: make the target match the local project Oct 20, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: In review

Development

Successfully merging this pull request may close these issues.

3 participants