---
name: post-build-flow
description: >-
  Handles workflow verification and setup after build-workflow succeeds, or when
  the message contains workflow-verification-follow-up or workflow-setup-required.
  Load after direct builds, when verificationReadiness requires action, or on
  orchestrator verify/setup follow-up turns.
recommended_tools:
  - ask-user
  - verify-built-workflow
  - workflows
  - build-workflow
  - executions
---

# Post-Build Flow

Use this skill after `build-workflow` succeeds on a direct orchestrator build,
especially when the build result contains `postBuildFlow.required: true`, or when
the current message contains `<workflow-verification-follow-up>` or
`<workflow-setup-required>`.

One-off builds (`postBuildFlow.reason: "direct-one-off-build-succeeded"`) hand
off to the `one-off-operations` skill instead — verification is optional there
and completion is a live run with read-back. If both sets of instructions are
in context for a one-off build, the one-off flow wins.

These instructions are in English, but user-visible text you write while
following them stays in the user's conversation language.

For trigger `inputData` shapes, read
`${N8N_WORKSPACE_DIR}/knowledge-base/reference/trigger-input-data-shapes.md` in
the sandbox workspace when available, or load this skill's
`references/trigger-input-data-shapes.md` linked file.

## Verification follow-up

When the current message contains `<workflow-verification-follow-up>`, verify
immediately from the payload's `obligation` — do not acknowledge first. If the
obligation is `ready_to_verify` or `verifying`, call `verify-built-workflow`. Do
**not** call `workflows(action="setup")` in this turn and do **not** declare the
workflow finished if `outcome.setupRequirement.status === "required"` — setup is
routed automatically as a separate `<workflow-setup-required>` step after
verification.

## Setup follow-up

When the current message contains `<workflow-setup-required>`, your first action
is to call `workflows(action="setup")` with the `workflowId` from the payload. Do
not verify, do not ask, do not write a message first — the inline setup card in
the AI Assistant panel is the user-visible surface. If it returns `deferred:
true`, respect the user's choice and do not retry with any other setup tool.
A result carrying `skippedByUser` names credentials the user already passed on:
never re-open setup for those, in this turn or any later one — see
[Credentials the user skipped](#credentials-the-user-skipped).
After setup completes or is applied, follow
[Mocked verification live-test follow-up](#mocked-verification-live-test-follow-up)
if the payload or prior verification evidence says mocked credentials,
simulated node output, fixture overrides, temporary pin data, or another mocked
input was used.

### Choosing the credential type for a service

Pick in this order:

1. **A dedicated credential type** (`slackApi`, `notionApi`, …) whenever one
   exists — search with `credentials(action="search-types")`.
2. **Simplified Custom Auth** (`httpTemplatedCustomAuth`) for any service
   without a dedicated type whose auth is expressible as header/query/body
   values — which covers API keys and bearer tokens (`Authorization: Bearer
<token>` becomes `{"headers":{"Authorization":"Bearer {{api_key}}"}}`, not
   `httpBearerAuth`). Always provide a recipe (below) so the user only pastes
   their secret.
3. **Plain generic types** (`httpBasicAuth`, `httpDigestAuth`, `oAuth2Api`, …)
   only for what a template cannot express: basic auth's base64-encoded pair,
   digest's challenge-response, OAuth flows — or when the user explicitly asks
   for a specific plain type: an explicit user choice wins (setup accepts it
   with `allowPlainGenericAuth: true`).

### Credential recipes for Simplified Custom Auth

When the workflow authenticates a service through Simplified Custom Auth,
include `credentialHints` in the same `workflows(action="setup")` call so the
setup card pre-fills the credential and the user only pastes their secret —
instead of facing an empty JSON template they'd have to decode from the
provider's docs. Before composing the hints, load the
`credential-recipe-research` skill and execute its lookup procedure — the
template, `docsUrl` and `testUrl` must come from the provider documentation
it has you fetch, never from memory:

- `template` — the auth request parts (headers/qs/body) exactly as documented,
  with `{{placeholder}}` markers where the user's values go.
- `placeholders` — one entry per marker: `name`, user-facing `title`, an
  optional `info` clarifying the value itself — its format or which of the
  provider's tokens it is (e.g. "Starts with tvly-"). Never where to obtain
  it, and never a URL or domain: the user asks the AI Assistant for that from
  the credential form. `type` is `password` unless clearly non-secret (at
  least one placeholder must stay `password`). Add `optional: true` only when
  the provider documents the value as optional (e.g. an org/region
  qualifier) — template entries referencing an empty optional placeholder are
  omitted from the request.
- `docsUrl` — the provider page where a logged-in user CREATES/COPIES the
  secret (e.g. `https://replicate.com/account/api-tokens`) — never the API
  reference. Not shown in the form: the AI Assistant help thread uses it to
  send the user to the exact page. Found via the `credential-recipe-research`
  procedure; omit when it finds nothing conclusive.
- `testUrl` — a documented side-effect-free GET that rejects a bad key with
  401/403, used to verify the credential on save and later retests; never one
  of the workflow's own endpoints, never anything billable. Found via the
  `credential-recipe-research` procedure; omit when nothing qualifies — a
  credential without a testUrl saves fine and honestly shows "could not be
  verified", which beats a false green.
- `acceptedStatusCodes` — almost always omit; the user can adjust it later on
  the credential if a service's auth answers 401/403 to valid GETs.
- `suggestedName` — display name for the created credential.

Example — fal.ai's docs say requests use `Authorization: Key <FAL_KEY>` and
`GET https://api.fal.ai/v1/models/usage` is a documented side-effect-free
endpoint that rejects a bad key (the model-serving host `fal.run` is not a
key-check endpoint):

```json
{
	"action": "setup",
	"workflowId": "...",
	"credentialHints": [
		{
			"suggestedName": "fal.ai API Key",
			"template": {
				"headers": { "Authorization": "Key {{api_key}}" }
			},
			"placeholders": [
				{
					"name": "api_key",
					"title": "fal.ai API key",
					"info": "Key ID and secret, separated by a colon",
					"type": "password"
				}
			],
			"docsUrl": "https://fal.ai/dashboard/keys",
			"testUrl": "https://api.fal.ai/v1/models/usage"
		}
	]
}
```

Never put a real secret in a recipe — the user pastes it in the setup card and
it is stored redacted in the credential. Add `nodeName` when several nodes use
Simplified Custom Auth for different services. You cannot see the secret, but
once setup reports the credential applied, treat it as fully configured — the
`{{placeholder}}` markers live only in the template; the stored values replace
them at request time. If a live test later fails with an auth error, that is
the moment to have the user re-open the credential and re-paste the value.

If the user defers setup instead, don't hand them manual field-by-field
credential instructions for the n8n editor — tell them to reopen setup when
they're ready: the card pre-fills everything except their key.

### Credentials the user skipped

Skipping is remembered for the whole conversation. A setup result may carry
`skippedByUser` (nodes and credential types the user passed on), and a build
outcome may carry `setupRequirement.status === "not_required"` with
`reason: "skipped-by-user"`. In both cases the blocking setup card is off the
table for those credentials — including after later edits, rebuilds, and
`<workflow-setup-required>` steps. Asking again is the single most common
complaint about this flow.

Instead, in your normal message:

- name what stays unconfigured and what happens at runtime (e.g. "the Slack post
  will fail until a channel is selected; the email still sends"),
- offer to set it up whenever they want.

Only once the user asks for a specific credential — "connect Slack now", "let's
do the Slack setup", or picking it out of an offer you made — call
`workflows(action="setup", reopenSkipped: ["slackApi"])`, naming just what they
asked for so the rest stays skipped. A generic "yes" to an unrelated question is
not an ask.

Pass the `reopenWith` value the tool reported for that card, not the user's
wording — a credential type for a credential card, a node name for one that was
only missing a parameter. If nothing matches, setup answers with
`unknown_reopen_target` and the list you can choose from; pick from it or tell
the user what they named isn't part of this workflow. Don't fall back to
re-offering, the user already asked.

## Publishing and testing

**Publishing is never required for testing.** Both `executions(action="run")` and
`verify-built-workflow` inject `inputData` as the trigger's output — the
workflow does not need to be active. Form, webhook, chat, and other event-based
triggers are all testable while the workflow is unpublished. Never publish a
workflow as a precondition for running it.

Do not proactively offer, recommend, or mention publishing until a successful
execution has run every required node on the claimed path without mocked
credentials, simulated node output, fixture overrides, or temporary pin data
for those nodes. A successful verification that used any of these is not
publish-readiness evidence. If the user explicitly asks to publish before a
live execution succeeds, warn that the live path remains untested, then follow
the requested publish flow.

`workflows(action="publish")` enforces this. While the latest verification left
nodes unreached or simulated, the call is refused and returns
`verificationDisclosure` — the coverage facts, generated from the run. Relay
those facts to the user and offer a live end-to-end test. Publish only if they
still ask, by calling publish again with `acknowledgeUnverified: true`. Never
set that flag to skip the disclosure. The user also sees the same facts in the
publish approval prompt, so a summary that contradicts them is visible to them.

Execution evidence can come from a run you started or a run the user started.
If the user says they ran the workflow manually, call
`executions(action="list", workflowId)`, identify the relevant run, and inspect
it with `executions(action="get", executionId)`. The user's statement alone is
not execution evidence. A user-run execution satisfies the publishing gate only
when the inspected result confirms success and that every required node on the
claimed path ran. Do not count it if mocked, simulated, fixture, or pinned output
was used. You may offer publishing after that confirmation.

For workflows produced by `build-workflow`, **always verify with
`verify-built-workflow`, never with raw `executions(action="run")`.** It reuses
the build outcome simulation plan, mocked credentials, and temporary pin data, so
destructive nodes are pinned and it is safe to call repeatedly. A raw
`executions(action="run")` runs the workflow live with no pin data, and on a
workflow you just verified it surfaces a redundant run-approval prompt to the
user right after verification already executed the workflow. For follow-up
requests like "verify again", call `verify-built-workflow` with `workflowId` even
if the original `workItemId` is not in context. For alternate deterministic
scenarios, pass `fixtureOverrides` keyed by simulated node name instead of trying
to force data through the trigger.

**Reserve `executions(action="run")` for runs the user explicitly asked for**
(e.g. "run it now", "execute it against my real data"). Never call it on your own
to re-test, expand coverage, or "prove the full chain" of a workflow you just
built or verified: re-run `verify-built-workflow` instead — with
`triggerNodeName` to reach another trigger's branch, or `fixtureOverrides` to
reach another branch within one trigger's run — or report the partial coverage
and let the user decide whether to run it.
If `fixtureOverrides` is rejected with `invalid_fixture_override`, the target
node was not classified as simulated in the build outcome. Do not retry the same
override. If that node's data controls a branch that needs verification and you
have the source file, load `workflow-builder`, declare representative `output`
fixtures on the controlling upstream node, rebuild the same workflow, and verify
again.

**Never edit or copy a saved workflow to reach a branch.** Disabling, deleting,
or reordering nodes to steer a test mutates the user's workflow and leaves it
broken for as long as the test runs — if it is published, its triggers fire
against the broken version. Building a throwaway second workflow is no better:
the evidence is gathered against a copy that can drift from the workflow the
user keeps, and the copy is left behind whenever the cleanup delete fails.

For a workflow with more than one trigger (`triggerNodes` has multiple entries),
**verify once per trigger**:

- Pass `triggerNodeName` to `verify-built-workflow` and call it once for each
  entry in `triggerNodes`. Naming no trigger verifies only the auto-detected
  one. An unresolvable name is rejected outright, so a rejected call means the
  name is wrong — re-read `triggerNodes`, never fall back to editing.
- Each pass covers its own trigger's branch, so its `nodesNotReached` will list
  the other triggers' nodes. That is expected, not a defect: coverage is the
  **union** across passes. Only treat a node as unverified once no pass reached
  it.
- Report per-trigger coverage — name each trigger and whether its branch ran.
  Claim the workflow is verified only when every trigger's branch has a
  successful pass.
- When the user asked for a live run, pass `triggerNodeName` to
  `executions(action="run")` the same way — one run per trigger — and report
  each branch's result.

## After build-workflow succeeds

1. Read `workflowId`, `workItemId`, `triggerNodes`, `verificationReadiness`,
   `setupRequirement`, and `postBuildFlow` from the tool output. If the output
   is missing a `workflowId`, explain that the build did not submit.
   - Before treating a saved workflow as done, inspect the persisted workflow
     with `workflows(action="get-as-code", workflowId)` or read the bound
     workspace source file, and compare the actual graph to the user's requested
     outcome. Build/save success only means a workflow was saved; it does not
     prove the saved workflow is good.
   - If the persisted workflow is missing the requested outcome, has an obvious
     dead-end draft shape, or the verification evidence is weak, load the
     `workflow-builder` skill and patch the same workflow with `build-workflow`
     using the existing `workflowId` and `workItemId`; then inspect and verify
     again.
   - If `verificationReadiness.status === "already_verified"`, treat the
     workflow as verified and do **not** call `verify-built-workflow` again.

- If `verificationReadiness.status === "ready"`, call
  `verify-built-workflow` with the `workflowId`, the `workItemId` when you
  have it, and the trigger-appropriate `inputData` shape. When `triggerNodes`
  has more than one entry, call it once per trigger with `triggerNodeName`.
- If `verificationReadiness.status === "needs_setup"`, call
  `workflows(action="setup")` with the workflowId so the user can configure it
  through the inline setup card in the AI Assistant panel.
- If `verificationReadiness.status === "not_verifiable"`, do not infer
  lower-level verification conditions; use the readiness guidance to give a
  clear warning or manual-test note. This is a warning completion state, not
  a verified state and not an infinite blocker.

2. Judge coverage, not just status. A `verify-built-workflow` result with
   `success: true` but a non-empty `nodesNotReached` is **partial** evidence:
   the execution ended early (see `lastNodeExecuted` and `coverageNote`) and
   the listed nodes — including any planned simulations — never ran.
   - Most common cause: a lookup/query node returned zero items (n8n stops
     downstream nodes on empty item lists). If the dead-end is a Data Table
     lookup, insert a matching test row with `data-tables(action="insert-rows")`,
     re-run `verify-built-workflow`, and delete the test row afterwards. The same
     holds for data you seed anywhere else to unblock a run — it is yours to
     remove once the run is done (see "Cleaning up after a live test").
   - If you cannot seed the data source, report honestly: name which nodes
     were verified and which were not, and tell the user the unreached part
     needs a manual test. Do not start a live `executions(action="run")`
     yourself to reach those nodes; offer the user a test instead. Never claim
     end-to-end verification when `nodesNotReached` is non-empty — except for
     nodes another trigger's pass already reached, since per-trigger coverage
     is the union across passes.
   - If the unreached nodes sit behind IF/Switch logic controlled by a live or
     nondeterministic upstream node, and alternate-branch verification is part
     of this turn's goal, first try one source-file repair: add representative
     `output` fixtures to that upstream node, rebuild the same workflow, and
     re-run `verify-built-workflow` with `fixtureOverrides`. Only fall back to a
     manual-test note when you cannot safely patch the source or the repair
     budget is exhausted.
   - Relay `simulationNote` (nodes whose output was simulated) to the user
     whenever it is present.
3. After verification handling, if `setupRequirement.status === "required"` and
   setup has not already run for this build, call `workflows(action="setup")`
   with the workflowId.
4. When `workflows(action="setup")` opens the inline setup card, the card is the
   user-visible surface. Do not tell the user to open the editor, use the canvas,
   or click a Setup button; the user does not need to navigate anywhere.
5. When `workflows(action="setup")` returns `deferred: true`, or reports
   `skippedByUser`, or applies only part of the card, respect the user's
   decision — do not retry with `credentials(action="setup")`, another
   `workflows(action="setup")` call, or any other setup tool. `partial: true`
   with `nodesStillNeedingSetup` is not permission to re-open the card in the
   same turn: report what remains as described in
   [Credentials the user skipped](#credentials-the-user-skipped).
6. After setup completes or is applied, follow
   [Mocked verification live-test follow-up](#mocked-verification-live-test-follow-up)
   when the latest verification evidence used mocks or simulations. If this
   follow-up is due, ask only whether the user wants the live test. Do not
   mention publishing or ask about the error workflow in the same response.
   If `credentialResolutionNote` says Gateway credits are depleted,
   that note wins: do not offer a live test.
7. Before your final summary, scan the **whole conversation** for live runs that
   already wrote test data into an external system — earlier turns included, not
   just this one. For each such record still sitting there, follow
   [Cleaning up after a live test](#cleaning-up-after-a-live-test): name it and
   offer to remove it. This is about data that **already exists** — a promise to
   clean up after some future run does not discharge it, and neither does the
   user's silence. If a later run failed, that says nothing about records an
   earlier successful run left behind; they are still there.
8. If testing has not already been offered or completed, ask whether the user
   wants to te