Docs/Infohub (Flow)/Exit Codes and Error Handling
Infohub (Flow)How-to

Exit Codes and Error Handling

What each exit code means, why a success message is not proof, and how to make a script fail loudly.

Updated 15 Oct 2026Infohub Docs

A script that carries on after a failed step produces a model that looks built and is not. This article is about making that impossible.

The exit codes#

Code Meaning
0 Success
1 Parse failure, a verb not available in this edition, or a model, template or deployment verb that refused or failed
5 A database-level verb completed with a warning
10 A database-level verb failed

Exit 10 covers the database-level verbs: --create, --migrate, --backup, --import, and --license. It means the operation failed — not specifically a migration problem, which is a reasonable but wrong assumption to make from the number.

Exit 5 is the same family's warning code. --migrate returns it when the migration ran but the instance's version property did not change, and --license returns it when the key was saved but could not be validated or its maintenance has already expired. Treat it as a failure in a pipeline: the instance is not in the state you asked for.

Exit 1 covers a malformed command line and the verbs gated out of Flow 7.2.0, both caught before anything is changed. It is also the code every other verb uses when it refuses or fails — a missing --entity-id, an entity that does not exist, a precondition the verb checks (an unreleased template, a measure on several calendars with no calendar named, a Flow Bootstrap that rejected a registration). The message on stderr, and in the log, says which.

Check the exit code, always#

The single most important rule when scripting Infohub:

& $exe @conn --create --name "Demo Plant"
if ($LASTEXITCODE -ne 0) { throw "create failed with exit code $LASTEXITCODE" }

In bash:

"Flow Config.exe" ... --create --name "Demo Plant" || exit 1

Or make the whole script fail fast:

set -euo pipefail
Important

A success message on stdout is not proof of success. Read the exit code, not the output. Success lines are gated on the exit code in 7.2.0, so the two now agree — but the habit of trusting printed text rather than the code is what makes scripts fragile across versions, and it is the habit worth breaking regardless.

Why --create deserves special attention#

--create lays the schema down as a base version and then upgrades it. If it throws part-way — an unreachable host, bad credentials, an assembly that will not load — the result is a partially created database: some of the tables, not all of them.

In 7.2.0 that failure correctly exits 10. A script that checks the exit code stops there and reports a real error.

A script that does not check it proceeds to build a model against a half-built schema, and every subsequent step fails in confusing ways that look like model problems rather than a failed create.

Tip

Verify the create, not just its exit code. For anything important, confirm the instance is usable before building on it:

& $exe @conn --create --name "Demo Plant"
if ($LASTEXITCODE -ne 0) { throw "create failed ($LASTEXITCODE)" }

# prove it responds
& $exe @conn --list-connection-types --output-format json > $null
if ($LASTEXITCODE -ne 0) { throw "instance created but not usable" }

Assert the outcome, not just the absence of failure#

Exit codes catch failures the CLI knows about. They cannot catch a command that succeeded and did less than you intended.

The clearest example is template instantiation. An instantiation that creates zero entities still exits 0, with a warning — it is how the CLI reports that matching instances already exist in the destination. (Instantiating a template that has not been released used to produce the same silent zero; it is now refused with exit 1 and a message naming the --release command to run.) A script that checks only the exit code proceeds to build charts against measures that do not exist.

So for operations where "did nothing" is a plausible outcome, assert the result:

# instantiate three units
& $exe @conn --instantiate --source-entity-type Metric --source-entity-id $tpl --destination-entity-type MetricFolder --destination-entity-id $dest --naming-pattern "Unit {0}" --instance-count 3
if ($LASTEXITCODE -ne 0) { throw "instantiate failed" }

# assert three arrived
$metrics = & $exe @conn --list-metrics --metric-folder-id $dest --output-format json | ConvertFrom-Json
if (@($metrics | Where-Object { $_.Name -like 'Unit *' }).Count -ne 3) { throw "expected 3 instances in folder $dest" }

Operations worth asserting after:

Operation Assert
--instantiate The expected number of entities exist
--create-chart plus measure adds --list-chart-tree shows the series
--import-model The entities arrived, and resolved to the right connections
--deploy --deployment-status shows the component as deployed
Any bulk create The count matches what you asked for
--delete-entity The entity is gone from the matching --list-* verb

This is the difference between a script that reports success and a script that produces a working model.

Capture the log#

Use --logfile on every step in an automated run, and keep the logs as build artefacts. When a pipeline fails at step 340, the log is what tells you why.

& $exe @conn --logfile "$logDir\step-$n.log" --create-measure ...

In 7.2.0 the log is written even for parse errors and gated verbs, so a failing step always leaves evidence. On earlier builds it did not, which is worth knowing if you maintain scripts targeting an older Infohub.

Write one log per step, or one per run with a step marker — either is fine, as long as a failure is attributable.

Continue or stop?#

Two defensible strategies, and the choice should be deliberate.

Stop on first failure. Correct for a promotion pipeline, where a partially applied change is worse than no change.

Continue and report. Correct for a large model build, where one failed measure out of four hundred should not discard the run — provided the script keeps a tally and fails at the end if anything failed.

Infohub's own demo-model build scripts take the second approach: each step logs pass or gap, the run continues, and the summary reports what did not work. That is the right shape for building, because it surfaces all the problems in one run rather than one per run.

What is never acceptable is continuing silently. If a step failed and the run exits 0 with no summary, the script is lying about its outcome.

Distinguishing the failure classes#

When a step fails, the code tells you which kind of problem you have:

Code Look at
1, before anything ran The command line itself — a typo, a wrong option name, or a gated verb
1, from a verb The message — a missing argument, an ID that does not exist, or a precondition the verb refused on
5 The warning — the migration or licence step did not leave the instance where you expected
10 The database or the environment — connectivity, credentials, permissions, disk
0 but wrong result The logic — instances that already existed, or a chart with no series

The last row is the one that consumes afternoons, and asserting outcomes is how you convert it into an immediate, clear failure.

Several verbs that once reported success and did nothing now refuse instead, which moves them from the last row to the second:

Verb Now refused with exit 1 when
--instantiate, --derive The source template is not released (locally, or on the publishing instance for a template-server source)
--update-from-template, --update-all-out-of-sync The entity's template is not released; a failed update also now prints the import's reason
--register-with-bootstrap Flow Bootstrap answered but rejected the registration — bad credentials, an unreachable instance database, an instance with no identity — and the response body is printed
--attach-measure-consumer The instance has no Integration Engine, or the measure sits on more than one calendar or event scheme and neither --time-scheme-id nor --event-scheme-id says which
--attach-measure-event-scheme The measure aggregates a different event scheme
--create-metric-folder, --create-template-folder The parent's type cannot hold the folder (a template metric folder at root, for example)

Idempotency and re-runs#

A script that fails halfway will be re-run. If re-running produces duplicates, every failure becomes a manual cleanup.

A few verbs are idempotent by design — the lookup-row creates --create-chart-component-type and --create-chart-item-type, and --attach-measure-event-scheme, which re-writes the event scheme sets on a measure that already carries the scheme rather than adding a second link. Everywhere else, check before creating. See Scripting Patterns and Idempotency.

Several verbs also take --dry-run and report what they would change without changing it: --delete-entity, --gap-skip-to, --import-model, --update-all-out-of-sync, --deploy and --undeploy. A dry run is the cheapest rehearsal of a step you cannot easily undo.

A robust step wrapper#

Worth writing once and reusing:

function Invoke-FlowStep {
    param([string]$Description, [string[]]$Arguments)

    $script:stepNo++
    $log = Join-Path $logDir ("{0:d4}.log" -f $script:stepNo)

    & $exe @conn --logfile $log @Arguments | Out-Null

    if ($LASTEXITCODE -ne 0) {
        $script:failures += "[$script:stepNo] $Description -> exit $LASTEXITCODE (see $log)"
        Write-Host "  FAIL  $Description (exit $LASTEXITCODE)"
        return $false
    }
    Write-Host "  ok    $Description"
    return $true
}

With a summary at the end that sets the script's own exit code:

if ($failures.Count -gt 0) {
    Write-Host "`n$($failures.Count) step(s) failed:"
    $failures | ForEach-Object { Write-Host "  $_" }
    exit 1
}
Write-Host "`nAll $stepNo steps succeeded."

Every step logged, every failure attributable, and the script's own exit code honest about the outcome. That last part matters most — a build script that exits 0 after failures is worse than one that never ran.

Checklist for a script you will rely on#

  1. Check $LASTEXITCODE after every invocation.
  2. Assert the outcome of anything where "did nothing" is possible.
  3. Use --logfile on every step and keep the logs.
  4. Take credentials from the environment, never hard-coded.
  5. Decide stop-on-failure or continue-and-report, explicitly.
  6. Make the script's own exit code reflect whether anything failed.
  7. Verify the instance is usable after --create before building on it.
esc
↑↓ navigate · ↵ open · ⌘K anywhere