`.dowhile` and `.dountil` `runCount` is stuck at `-1`

I don't know why this is a thing but it is. How long has this been broken for? or is this a recent regression? Is this a known issue? also dowhile and dountil are the same function, just reverse the true to false, vice versa, why have 2? I was under the impression that dowhile ran its condition check before the loop whereas dountil ran its check after the loop, im that case it would warrant seperate functions.
const testReturnStep = createStep({
id: "test-step",
description: "Tests the step",
inputSchema: z.object(),
outputSchema: z.object({
done: z.boolean(),
}),
execute: async ({ inputData }) => {
return {
done: true,
};
},
});

const testStep = createStep({
id: "test-step",
description: "Tests the step",
inputSchema: z.object({
type: z.enum(["test", "test2"]),
}),
outputSchema: z.object({}),
execute: async ({ inputData }) => {
return {};
},
});

const testWorkflow = createWorkflow({
id: "test-workflow",
description: "Tests the workflow",
inputSchema: z.object({
type: z.enum(["test", "test2"]),
}),
outputSchema: z.object({
done: z.boolean(),
}),
})
.map(async ({ inputData }) => {
return inputData.type;
})
.branch([[async ({ inputData }) => inputData === "test", testStep]]) // no type-safty
.map(async ({ inputData }) => ({ done: true }))
.then(testReturnStep) // no type-safty here either
.commit();

export const chatWorkflow = createWorkflow({
id: "chat-workflow",
description: "Runs a chat",
inputSchema: z.object({}),
outputSchema: z.object({}),
})
.map(async ({ inputData }) => ({ type: "test2" }))
.dountil(testWorkflow, async ({ inputData, runCount }) => {
console.log("runCount", runCount);

if (runCount >= 10) return true;
return false;
})
.commit();
const testReturnStep = createStep({
id: "test-step",
description: "Tests the step",
inputSchema: z.object(),
outputSchema: z.object({
done: z.boolean(),
}),
execute: async ({ inputData }) => {
return {
done: true,
};
},
});

const testStep = createStep({
id: "test-step",
description: "Tests the step",
inputSchema: z.object({
type: z.enum(["test", "test2"]),
}),
outputSchema: z.object({}),
execute: async ({ inputData }) => {
return {};
},
});

const testWorkflow = createWorkflow({
id: "test-workflow",
description: "Tests the workflow",
inputSchema: z.object({
type: z.enum(["test", "test2"]),
}),
outputSchema: z.object({
done: z.boolean(),
}),
})
.map(async ({ inputData }) => {
return inputData.type;
})
.branch([[async ({ inputData }) => inputData === "test", testStep]]) // no type-safty
.map(async ({ inputData }) => ({ done: true }))
.then(testReturnStep) // no type-safty here either
.commit();

export const chatWorkflow = createWorkflow({
id: "chat-workflow",
description: "Runs a chat",
inputSchema: z.object({}),
outputSchema: z.object({}),
})
.map(async ({ inputData }) => ({ type: "test2" }))
.dountil(testWorkflow, async ({ inputData, runCount }) => {
console.log("runCount", runCount);

if (runCount >= 10) return true;
return false;
})
.commit();
Pardon the example, I am planning on using it in another problem, so it overlaps with that problem, but this still highlights the issue well.
3 Replies
Raid55
Raid55OP3w ago
because of this issue, there is no way of stopping infinit loops that might arise, since if there is an error, and the condition is not meet, it will loop forever. here is an example of that using the code above.
const testReturnStep = createStep({
id: "test-step",
description: "Tests the step",
inputSchema: z.any(), // what would even be the typescript here? branch does not have typesafety in its return and returns a undocumented type anyway
outputSchema: z.object({
done: z.boolean(),
}),
execute: async ({ inputData }) => {
console.log("inputData", inputData);
return {
done: inputData.done,
};
},
});

const testStep = createStep({
id: "test-step",
description: "Tests the step",
inputSchema: z.object({
type: z.enum(["test", "test2"]),
}),
outputSchema: z.object({
done: z.boolean(),
}),
execute: async ({ inputData }) => {
return {
done: true,
};
},
});

const testWorkflow = createWorkflow({
id: "test-workflow",
description: "Tests the workflow",
inputSchema: z.object({
type: z.enum(["test", "test2"]),
}),
outputSchema: z.object({
done: z.boolean(),
}),
})
.map(async ({ inputData }) => {
return inputData.type;
})
.branch([
[async ({ inputData }) => inputData === "test", testStep],
[async ({ inputData }) => inputData.lol === "test3", testStep],
]) // no type-safety
.then(testReturnStep)
.commit();

export const chatWorkflow = createWorkflow({
id: "chat-workflow",
description: "Runs a chat",
inputSchema: z.object({}),
outputSchema: z.object({}),
})
.map(async ({ inputData }) => ({ type: "test2" }))
.dountil(testWorkflow, async ({ inputData, runCount }) => {
console.log("runCount", runCount);

if (inputData.done) return true;
return false;
})
.commit();
const testReturnStep = createStep({
id: "test-step",
description: "Tests the step",
inputSchema: z.any(), // what would even be the typescript here? branch does not have typesafety in its return and returns a undocumented type anyway
outputSchema: z.object({
done: z.boolean(),
}),
execute: async ({ inputData }) => {
console.log("inputData", inputData);
return {
done: inputData.done,
};
},
});

const testStep = createStep({
id: "test-step",
description: "Tests the step",
inputSchema: z.object({
type: z.enum(["test", "test2"]),
}),
outputSchema: z.object({
done: z.boolean(),
}),
execute: async ({ inputData }) => {
return {
done: true,
};
},
});

const testWorkflow = createWorkflow({
id: "test-workflow",
description: "Tests the workflow",
inputSchema: z.object({
type: z.enum(["test", "test2"]),
}),
outputSchema: z.object({
done: z.boolean(),
}),
})
.map(async ({ inputData }) => {
return inputData.type;
})
.branch([
[async ({ inputData }) => inputData === "test", testStep],
[async ({ inputData }) => inputData.lol === "test3", testStep],
]) // no type-safety
.then(testReturnStep)
.commit();

export const chatWorkflow = createWorkflow({
id: "chat-workflow",
description: "Runs a chat",
inputSchema: z.object({}),
outputSchema: z.object({}),
})
.map(async ({ inputData }) => ({ type: "test2" }))
.dountil(testWorkflow, async ({ inputData, runCount }) => {
console.log("runCount", runCount);

if (inputData.done) return true;
return false;
})
.commit();
this above will cause an infinite loop with no way to stop it. And the worst issue here is the observability. If we are using any paid observability option, we pay per trace once we hit a certain amount, observability right now will just run rampant in these infinite loops. There is no cap on the trace amount per parent. @Daniel Lew @Tyler @Romain @Eric (tagged you here eric because this is also an observability issue, we should allow for a cap on traces regardless to ensure this cannot happen even if a bug arises in the future, i broke langfuse because one of those traces went through, it crashed their frontend, no kidding) i am tagging you guys here because this is actually pretty important and needs to get fixed asap, as shown above, if the looping workflow fails to return something that would mark it as "done" weather it be a "idx" or a "done" bool, it just loops forever. I just talked about this to my co-worker and he said he ran into this issue in early September, posted about it and it got lost in the channel. https://discord.com/channels/1309558646228779139/1310121281177387109/1414002570548089044
Mastra Triager
GitHub
[DISCORD:1423827598475005952] .dowhile and .dountil runCount ...
This issue was created from Discord post: https://discord.com/channels/1309558646228779139/1423827598475005952 I don't know why this is a thing but it is. How long has this been broken for? or ...
rase-
rase-3w ago
Looks like runCount is only intended to track retries, so it's not named very appropriately. Made a note to change that through our deprecation/breaking change protocol. @Taofeeq and I will look into adding an iteration count to loop conditions so you'll be able to track this. It's important to not conflate both under runCount (or whatever we end up renaming it to, probably retryCount), because they track two very different things. cc @Abhi Aiyer here as well

Did you find this page helpful?