Null workflow steps appearing out of nowhere or after a merge

Our team has been experiencing an issue where workflow steps are unexpectedly appearing as (null), even in workflows that no one on the team has touched.

We’ve also seen this happen to workflows that were developed, tested, and passed QA in a developer’s branch, but then had steps become (null) after being merged into staging.

I was curious whether any other developers have experienced this issue as well.

Yes, experienced it

Send a bug report with the workflow link (in the editor), app ID, and time (so they can restore to the savepoint to investigate it) to Bubble at Contact | Bubble

You can resolve it by adding an action to the workflow and then deleting it. But it’s a nasty bug because it doesn’t flag in issue checker at all and the workflow fails silently.

Yes, we’ve been experiencing the same issue and have been resolving it in the same way. We’ve submitted a bug report to Bubble, and I’ll update this thread when we hear back from them.

This is definitely one of the nastier bugs we’ve encountered. Thank you for your comment as well!

I’m curious to hear your thoughts. Do you have any ideas about what might be causing this?

Are you or your team using Buildprint? I don’t think it’s related but all of our client apps use it so it’s hard for me to check if it’s a result of Buildprint or not.

Our team does use Buildprint for merging and for parts of development.

I did notice this happend without merging with buildprint as well on a merge today so i would lean more towards a bubble issue. Workflow for a button was not worked on element was but that buttons workflow steps became null upon q/a in that staging branch after the merge.

FWIW I’ve debugged the issue and what causes it in the Bubble editor; I just haven’t worked out if it’s Bubble or Buildprint.

I’ve never experienced something like that. You mean the workflow sTwo itself is like a blank shell in the series or the data processed by the step is written as null and not actually just empty data?

If it’s the actual workflow action step reading as null and not picked up by issue checker is this only in areas where a merge took place from branches not altered by a developer?

Basically, Bubble stores your app JSON (the source code of your app), ‘shrunk’.

{"pages": <xxx>} for example, is stored as {"%p3": <xxx>} with the shrunk key.

In the bugged workflows, the action’s action types are stored as unshrunk "type" rather than shrunk %x.

Who would do such a silly thing?

Buildprint had an edge case during its logic that resolves merge conflicts that meant that it would write these unshrunk.

Georgecollier explained it perfectly here would be an example of what that can look like for reference

@Paolo_42 a fix is out that will apply to all new conversations going forward. If using the CLI, use buildprint update to make sure you’re on the latest version.

Yeah, I thought it might have been an ID issue or something similar if messing with App JSON was the cause

This is what I’ve been scared of since you announced Buildprint, lol.

Meh, easily resolvable

Bubble could ship the same bug with the same effect

Is it a factor of AI just not following instructions or a factor of instructions not provided to the AI?

neither, it’s an issue with our issue checker which is programmatic

people think buildprint lets AI just write Bubble JSON but that’s a huge simplification and there’s a ton of validation and issue checking behind the scenes

does the fix allow buildprint to catch existing cases of the issue? or do i have claude cowork go check every single one of my workflows again? lol

If you run this in the editor it’ll autofix any workflows that are broken (if there are any!)

(() => {
  window.enable_loading_root = true;
  const app = appquery.app().json;
  const nodes = [...new Set(Object.values(app.read_index("id_to_path")))]
    .map(path => app.by_path(path));
  const indexed = new Set(nodes.map(node => node._path()));

  const malformed = (node, value) => {
    if (value == null || typeof value !== "object") return false;
    if (Array.isArray(value)) {
      return value.some((child, i) =>
        malformed(node._child(String(i)), child)
      );
    }
    return Object.entries(value).some(([key, value]) => {
      const child = node._child(key);
      return (
        (!key.startsWith("%") && node.child(key)._name() !== key) ||
        (!indexed.has(child._path()) && malformed(child, value))
      );
    });
  };

  const expand = (node, value) => {
    if (value == null || typeof value !== "object") return value;
    if (Array.isArray(value)) {
      return value.map((child, i) =>
        expand(node._child(String(i)), child)
      );
    }
    const result = {};
    for (const key of Object.keys(value).sort(
      (a, b) => Number(a.startsWith("%")) - Number(b.startsWith("%"))
    )) {
      const child = node._child(key);
      result[child.name()] = expand(child, value[key]);
    }
    return result;
  };

  nodes.forEach(node => node.ensure_loading());

  u.run_once(300, () => nodes.map(node => node._raw()), (error, values) => {
    if (error) return console.error(error);

    const contaminated = nodes.filter((node, i) =>
      malformed(node, values[i])
    );
    const roots = contaminated.filter(node =>
      !contaminated.some(other =>
        other !== node && node._path().startsWith(`${other._path()}.`)
      )
    );

    roots.forEach(node => node.set(expand(node, node._raw())));
    console.log(`Repaired ${roots.length} objects.`);
  });
})();

Edit: I skipped a flag you need first :slight_smile:

haha yes I added the flag. it says it made some 400+ corrections though which seems dubious haha but if thats true then great I suppose!

Basically most of Bubble’s logic handles whether it’s stored with shrunk/unshrunk keys, but some part of the merge logic clearly doesn’t so the specific case that would be affected is if all of the true:

  • if you previously used Buildprint to merge
  • AND Buildprint resolved conflicts
  • AND one branch makes a future change to that workflow in a future merge