A run keeps the inputs its flow declared at submit

Run.declared (migration 2e7ca7d10044) snapshots the declarations on submit
and import. The run detail's Inputs panel reads them, and a run from before
falls back to the flow's current declarations.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WgBfkm4rCmtd1T4vHWjEPx
This commit is contained in:
2026-10-15 10:20:29 +02:00
co-authored by Claude Opus 5.5
parent edd01ee330
commit df2620cede
10 changed files with 77 additions and 6 deletions
+12
View File
@@ -3498,6 +3498,18 @@ export const RunDetailSchema = {
title: 'Flow Version',
default: 1
},
declared: {
anyOf: [
{
additionalProperties: true,
type: 'object'
},
{
type: 'null'
}
],
title: 'Declared'
},
nodes: {
items: {
'$ref': '#/components/schemas/RunNodeRow'
+3
View File
@@ -1189,6 +1189,9 @@ export type RunDetail = {
[key: string]: unknown;
};
flow_version?: number;
declared?: ({
[key: string]: unknown;
} | null);
nodes?: Array<RunNodeRow>;
artifacts?: Array<ArtifactRow>;
};
+7 -2
View File
@@ -44,7 +44,7 @@ export function RunDetail({ id }: { id: string }) {
const cancel = useCancelRun()
const retry = useRetryRun()
const names = useMetricNames(id)
const { declared, gone } = useFlowInputs(run?.flow)
const { declared: current, gone } = useFlowInputs(run?.flow)
const [metric, setMetric] = useState("")
if (isPending || !run) return <Skeleton className="h-96 w-full rounded-lg" />
@@ -58,6 +58,11 @@ export function RunDetail({ id }: { id: string }) {
// Said once, where it applies.
const cached = nodes.some((node) => node.status === "cached")
// What the flow declared when this run was submitted. A run from before
// that was kept reads against the flow's current declarations.
const declared = run.declared
? new Map(Object.entries(run.declared))
: current
// Declared order first, so the panel reads the way the flow does; anything
// the run carries that the flow no longer declares still shows, since it is
// what the run was actually given.
@@ -173,7 +178,7 @@ export function RunDetail({ id }: { id: string }) {
<h2 className="font-medium text-sm">Inputs</h2>
{fed.length === 0 ? (
<p className="text-muted-foreground text-sm">
{gone
{gone && !run.declared
? "The run carried no parameters of its own, and its flow is gone, so what it took from the flow cannot be read back."
: "This flow declares no inputs, so there was nothing to choose."}
</p>
+3 -3
View File
@@ -187,9 +187,9 @@ const isMissing = (error: unknown) =>
* ones it names and takes the flow's own for the rest — so this is what turns
* `run.params` from "what was passed" into "what the run actually ran with".
*
* The declarations are the flow's *current* ones, while a run carries the
* `flow_version` it was submitted against. An input added since is shown on an
* older run as a default it never actually received. And the flow may not be
* The declarations are the flow's *current* ones. A run keeps the ones it was
* submitted against in `run.declared`, and the run detail reads those, so this
* is the fallback for a run from before that was kept. And the flow may not be
* there at all: deleting one leaves its runs standing, so `gone` is an ordinary
* answer here rather than a fault, and the screen says so instead of drawing a
* run that declares nothing.