Skip to content

Keep the step ids a project already has - #1535

Closed
elias-ba wants to merge 7 commits into
mainfrom
recorded-step-ids
Closed

elias-ba wants to merge 7 commits into
mainfrom
recorded-step-ids

Conversation

@elias-ba

@elias-ba elias-ba commented Sep 9, 2026

Copy link
Copy Markdown
Collaborator

Short Description

Two steps called step 👍 and step 👎 both slugify to step, so on pull one overwrites the other and a step's code is gone. Two workflows named that way both become wf, and the pull crashes with step not found. This keeps the ids a project already has instead of working them out again.

Part of #1388, which asks for this directly: stop using slugify for identity.

Implementation Details

The file already stores each step's id, and the reader already takes it as written. Only the pull side ignores it. So a pull now keeps the ids the project has, and only works one out for a step it has not seen.

Nobody can hit the step case today because Lightning restricts step names to ASCII, but OpenFn/lightning#5106 lifts that. Workflow names have never been restricted, so that one is reachable now.

Ids are matched by name, since the files carry no uuids, so a rename still moves a step. There is a test for that.

QA Notes

Name two steps step 👍 and step 👎 and pull. You should get two .js files with the right code in each. On main you get one.

Same with two workflows. On main the pull throws.

AI Usage

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

You can read more details in our
Responsible AI Policy

A step's id is worked out from its name on every pull, and that conversion
drops anything not url-safe. Two names that differ only in an emoji or an
accent shorten to the same id, so the second step is written over the first.
Two steps go in and one file comes out, with no warning.

The file already holds each step's id and the reader already takes it as
written. Only the pull side ignores it. So a pull now reads the ids the
checked-out project has already given its steps and keeps them, deriving one
only for a step it has not seen. A derived id that is taken gets a numeric
suffix, and new steps are handled in uuid order so two people pulling the same
project separately reach the same answer.

Every name in a project today is ascii, so a kept id and a derived one are the
same string and nothing moves for anyone.

Edges named their target by deriving the target's name again. They now use the
resolved id, so a reference cannot disagree with the step it points at.

Ids are matched by name because the workflow file carries no uuids. That is why
renaming a step still moves it. Tying an id to a step across a rename needs the
uuid written down, which is a change to the file format and a separate question.
Review found three ways this could go wrong, two of them mine.

An id read back from a project file went straight into a path. A workflow file
can come from a repository somebody else prepared, so a step id of ../../.. was
written outside the project. Deriving the id on every pull used to launder that;
keeping it removed the only guard. Ids that are not already url-safe are now
ignored.

A step named constructor or toString found a function on Object.prototype, so
both steps ended up sharing an id and one lost its body. That fired on an
ordinary pull with nobody attacking anything.

Ids are recorded per workflow rather than in one flat map, since two workflows
can each hold a step of the same name, and both passes now run in uuid order so
the answer does not depend on how the server listed the jobs. A trigger's id is
its type and it shares the directory, so those are reserved before steps are
given theirs. The map no longer leaks into the workspace config.
@github-project-automation github-project-automation Bot moved this to New Issues in Core Sep 9, 2026
The hasOwnProperty call only mattered if a prototype member could pass
isSafeId, and it cannot: a function is not a string. Mutating it away failed
nothing, so it was a comment explaining code that did not need to be there.
The PR body told QA to expect a rename to move the step. That was a prediction
from how the matching works, not something anyone had run.
Two workflows called `wf 👍` and `wf 👎` both slugify to `wf`, and serialize
then throws `step not found`. So a pull crashes rather than quietly losing a
step, and unlike the step case this one is reachable today, since workflow
names have never been restricted.

The id resolution is now shared, so a workflow keeps the id it already has and
only gets a new one when it has not been seen. Recorded ids carry workflow ids
alongside step ids, which is why the config field is now recordedIds.
packages/cli/.state.json is written by running the CLI locally.
@josephjclark

Copy link
Copy Markdown
Collaborator

But this is circular no?

The step ids that were previously generated came on fetch from slugify. Preserving them is meaningless if they're not safe. And if they don't exist we still slugify them. So this just seems to be pushing the problem around and complicating the code.

Also I think this is just locking in local IDs (even if step names change).

There should be ways we can smartly and safely slugify to resolve conflict, I'd rather look at that.

I was also hoping for this validation to come from lightning to avoid getting in this situation in the first place. Otherwise Lightning will also have to resolve this if eg we put step names in URLs.

@josephjclark

Copy link
Copy Markdown
Collaborator

Closing as the linked issue OpenFn/lightning#5156 is a wontfix

@github-project-automation github-project-automation Bot moved this from New Issues to Done in Core Sep 9, 2026
@elias-ba
elias-ba deleted the recorded-step-ids branch September 9, 2026 17:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

3 participants