⚠️ This beta is intended for testing purposes only and should not be used in production environments.
⚠️ This beta release includes breaking changes. Read the Breaking changes section before upgrading — in particular the new cleanup of artifacts whose target has left the artifact definition's target group, and the stricter validation applied to POST /api/schema/load.
⚠️ Runtime versions. Infrahub 1.11 moves to Python 3.14 (from 3.13) and Neo4j 2026.05.0 (from 2025.10.1). If you run your own Neo4j rather than the version shipped with Infrahub, plan that database upgrade alongside this one.
v1.11.0b1 is the second beta of Infrahub 1.11.0. If you already read the v1.11.0b0 notes, start with New since v1.11.0b0 — everything else below is carried over unchanged.
The headline of 1.11 is scale and performance. Until now, almost any change caused Infrahub to redo work the change could not possibly have affected: merging a branch re-ran every Generator, regenerated every artifact and could recompute many computed attributes, and a single commit to a linked repository recomputed every transform-based computed attribute in the instance. On a large dataset that meant thousands of background tasks and an instance that stayed busy for tens of minutes after a merge. 1.11 makes that work proportional to what actually changed.
Alongside this, webhook deliveries become first-class tasks you can inspect, retry and cancel. A delivery used to be an anonymous step inside a larger task, so you could not see what was sent, read what came back, or resend anything without re-firing the original event. Each delivery is now its own task with its request, response and a plain-language failure reason.
The release centers on three themes: performance and scale — selective regeneration after a merge, precise Generator and computed-attribute triggers, and narrower schema validation; operability and recovery — webhook delivery operability, and detection of and recovery from a failed merge; and load management — priority lanes for the work users wait on, and API backpressure that protects interactive traffic when the instance is busy.
New since v1.11.0b0
- Store a bare IP address — the new
IPAddressattribute kind holds192.0.2.1without silently turning it into192.0.2.1/32. - Know what a schema load did not apply —
POST /api/schema/loadnow reports the fields it ignores instead of dropping them silently, and rejects a misspelled one. Breaking for a few payload shapes and for OpenAPI type names. - Allocate from an IP pool with a specific prefix length — instead of always taking the pool's default.
- Richer anonymous telemetry — the daily payload gains adoption and 24-hour activity signals.
- Merge failure and recovery are now documented — the write protection, the failed-merge state and
infrahub recover mergehave reference documentation. - A batch of fixes to precise recomputation, repository imports and the web UI — see Minor changes and the Full changelog.
Main changes
Regenerate only what a merge actually changed
When you merge a branch or a Proposed Change, Infrahub now dispatches only the artifact and Generator definitions whose inputs the merge actually changed, and only for the members affected — instead of regenerating every definition for every member.
A merge captures its own diff and uses it to decide what to run. A definition is dispatched when its GraphQL query, its definition object, or its code changed; members are narrowed to the objects the change touched. Across the scenarios used to validate this, the number of regeneration tasks a merge produces fell by between 73% and 100%.
The rule the selection follows is that regenerating too much is acceptable and regenerating too little is not. Every uncertain signal — a missing or unreadable diff, a definition with no content fingerprint yet, an incomplete dependency list — falls back to regenerating everything for that definition, so nothing is left stale. On a direct merge that runs a Generator, the artifacts that consume that Generator's output are regenerated once the Generator finishes.
The behavior is controlled by selective_execution_after_merge (INFRAHUB_SELECTIVE_EXECUTION_AFTER_MERGE) and is enabled by default. Setting it to false restores the previous full-regeneration behavior.
Re-run only the Generators a commit affected
Within a Proposed Change, a Generator is now re-run only when its source file, its GraphQL query, or its definition was affected by the commits in the branch — not on any file change anywhere in a linked repository. A typo fixed in an unrelated README no longer re-runs every Generator across every member of its target group.
This extends to Generators the precise regeneration that 1.10 introduced for artifacts. Each decision to run or skip is recorded in the Proposed Change's task log, naming the file, query, or definition field responsible, so you can see why a Generator ran. Read-only repositories now participate on the same terms, even on branches where sync_with_git is disabled.
For dependencies Infrahub cannot detect on its own — a helper module imported at runtime from a sibling package, for example — declare them with the optional watch: key on generator_definitions in .infrahub.yml:
generator_definitions:
- name: interface_generator
file_path: "generators/interfaces.py"
class_name: InterfaceGenerator
targets: network-devices
query: device_interfaces
watch:
files:
- "generators/shared/" # directory entries are recursive
- "generators/helpers/naming.py"Generators imported before this upgrade keep working unchanged and adopt the precise behavior automatically on their next import. Until then they conservatively re-run on any file change in their repository, so no output goes stale during the transition.
Recompute computed attributes only when their inputs change
Two separate causes of unnecessary recomputation are addressed.
- A commit to a linked repository used to recompute every transform-based computed attribute in the instance, for every object of its kind. Infrahub now recomputes only the attributes whose Python transform actually changed. An edit and its exact revert leave the transform unchanged and trigger no work at all.
- A schema change used to recompute every computed attribute on the branch. A computed attribute is now recomputed only when the change affects a schema element its value depends on, including elements reached through a relationship. Where the impact cannot be determined, Infrahub still falls back to a full recompute so nothing goes stale.
Underneath both, definitions that produce output from a repository — GraphQL queries, transformations, artifact definitions and Generator definitions — now carry a fingerprint attribute holding a content hash of their inputs. Because the fingerprint is branch-aware, a change to a definition surfaces the same way whether it arrived by a Git import, a branch merge, a rebase or a direct edit.
One behavior to be aware of: a Python transform that declares no watch: key has the commit id folded into its fingerprint, so it continues to recompute on every commit to its repository — but scoped to its own attributes only, rather than to every computed attribute in the instance. Declaring watch: with an explicit list (including an empty one) opts that transform into precise, commit-independent recomputation. The first import of a transform after upgrading recomputes its attributes once; that is a one-time cost, not a per-commit one.
Setting the watch property on any transform is now a best practice to make sure resources are only generated when needed. Here's an example for a Python Transformation that is used to generate a cabling plan artifact:
python_transforms:
- name: cabling_plan
class_name: CablingPlan
file_path: "./transforms/cabling_plan.py"
# cabling_plan.py imports from the src package (protocols), which the transform's
# auto-detected closure (its own directory) cannot follow. Declaring it here closes
# that gap and marks the closure complete, so the artifact only regenerates when a
# real dependency changes instead of on every unrelated commit.
watch:
files:
- src/infrahub_solution_ai_dc/protocols.pyMore information can be found in the documentation.
Since b0, two further sources of unnecessary work are gone. A Python computed attribute whose query follows a relationship to a generic no longer recomputes on updates to member kinds the query reads no field from — those triggers carried no field filter, so any update to such a node started a recompute whose own writes could match the same triggers again and cascade. And a Jinja2 transform is no longer reported as changed on every repository import, which was re-fingerprinting it and recomputing its attributes each time.
Return to normal faster after a large merge or rebase
Merging or rebasing a branch now runs a single combined recompute pass for computed attributes, display labels and human-friendly IDs, instead of one recompute job per changed node. The work after the operation scales with the number of derived values actually affected rather than with how many nodes were touched, and the resulting values are unchanged.
Recomputed values are also now written in bulk. Previously each value took its own update task and its own API call; results are now persisted together in bounded transactions. For computed attributes backed by a Python transform, a batch initializes the transform's Git repository once for the whole batch rather than once per object, and an object whose recomputed value is unchanged is skipped entirely — so it neither writes nor triggers further recomputation. That last point removes a self-sustaining cycle in which recomputing already-correct values dispatched more recomputation.
Deleting a node in a branch now also refreshes the readers of that node: after the merge or rebase, computed attributes, display labels and human-friendly IDs that read the deleted node across a relationship no longer keep naming it.
Validate only the constraints a change can violate
Schema constraint validation during Proposed Change checks, merges and rebases used to run against every kind in the schema, even for a branch that changed only data. It is now scoped twice over:
- to the kinds the change touched — plus the generics they inherit from and any kind whose uniqueness constraint reads an attribute of a changed kind;
- to the fields a constraint actually guards — changing an attribute that participates in no uniqueness constraint no longer re-validates that kind's uniqueness, and re-parenting a child no longer triggers the children check on the parent.
The amount of validation work no longer grows with the total size of your schema. Schema diffs between two identical branches now correctly come back empty.
Inspect, retry and cancel webhook deliveries
Every webhook delivery is now its own task, visible in the Tasks tab and from the webhook's related tasks panel. For each delivery you can see the request Infrahub sent, the response it received, and the number of attempts made.
- Failures are readable. An expected delivery failure — an unreachable target, a TLS problem, a timeout, an HTTP error from the receiver, a misconfiguration — is reported as a classified reason with a hint on what to check, instead of a raw Python stack trace. Unexpected errors keep their trace, so a genuine defect still looks like one.
- Deliveries retry on their own. A failed delivery retries automatically with a delay between attempts, rather than exhausting its retries in a few seconds as before. Retries stop early for failures that cannot succeed on a retry, such as a 4xx response or a bad configuration.
- You can retry a settled delivery. Retrying resends the payload that was captured when the event fired, against the webhook's current configuration — so fixing a wrong URL, header or signing key and then retrying works as expected. The original delivery is preserved as a record.
- You can cancel one still in flight. Cancelling a delivery stops its remaining attempts.
A delivery still waiting between automatic attempts is no longer misreported as crashed, and a TLS certificate verification failure against any external endpoint — a webhook target or an SSO provider — is now reported as a TLS error rather than a generic connection error.
Recover from a failed merge
If the worker running a merge is killed partway through, the instance used to be left with a half-merged default branch, a branch stuck in MERGING, and no way back other than restoring a backup or running manual database queries. 1.11 makes that state detectable, visible, and recoverable.
- Writes are blocked for the duration of a merge. Writes to both the default branch and the source branch are refused while a merge runs, and a new merge or rebase is refused while one is in progress. A blocked write receives a transient message asking the caller to retry shortly, and the block lifts automatically when the merge finishes or is rolled back.
- A dead merge is detected. Infrahub notices when a merge's worker is no longer running, records the branch as failed, and keeps the default branch protected. Writes then receive a distinct, non-retryable error telling the operator that recovery is required — separate from the transient "merge in progress" case, so a client does not retry forever.
infrahub recover mergeputs things back. The new command rolls back the partial merge on the default branch, resets the branch and any associated Proposed Change toOPEN, and lifts the write protection. It shows you what it will do and asks for confirmation (--yesto skip), and it is safe to run twice — a run with nothing to recover reports that and changes nothing. Object, attribute and relationship timestamps are restored to their pre-merge values, including for objects affected by a schema migration.
Branch-status rejections now also carry structured GraphQL error codes — BRANCH_ALREADY_MERGED, BRANCH_NEEDS_REBASE and MERGE_IN_PROGRESS — so API and SDK clients can react to a code instead of matching message text.
New in b1: this is documented. Merge a branch now describes the write protection, the errors an operator or a client sees in each state, and the recovery procedure, and the infrahub recover CLI reference covers the command itself.
Keep interactive work moving while background work runs
Background work and the operations a user is waiting on used to share a single queue, so clicking "merge" while the instance worked through a backlog meant waiting behind all of it.
Infrahub now runs three priority lanes and classifies work between them. Branch operations (create, merge, rebase, delete, validate), Proposed Change merges, Generator definition runs, schema load and check, transform rendering and on-demand artifact generation all dispatch on the high lane. Work whose result nobody is waiting on moves to the low lane: profile refresh, and post-merge follow-ups such as Proposed Change cancellation, automatic branch deletion and artifact and Generator regeneration — which previously inherited the merge's own high priority. IPAM reconciliation stays in the middle.
Priority travels through a whole chain of tasks, so a sub-task started by a high-priority operation runs at the same priority. The queues are created automatically on startup, so an upgraded instance needs no manual migration.
Keep the UI responsive when the instance is busy
Heavy background work and the web UI compete for the same finite API and database capacity. Because the API could not tell the two apart, a busy instance could stop serving the UI altogether and the application appeared to hang.
Requests can now declare their priority with an X-Priority header (high, medium or low; anything missing or invalid is treated as medium). Under sustained overload Infrahub sheds lower-priority requests first, returning 429 Too Many Requests with a Retry-After header before the request does any work, so interactive traffic keeps flowing. The web UI sends high on its own calls and low on opt-in background traffic.
Shedding responds to two independent signals: how long requests are waiting for capacity, and how much slower the database is currently responding than its measured best. It is graduated rather than all-or-nothing — as pressure rises, a growing fraction of a priority class is shed, low first, then medium, with high-priority traffic protected until the database is under extreme load. On a normally loaded deployment the layer does nothing. Eight infrahub_admission_* metric families and three database-stress gauges are exposed on /metrics, and the whole layer can be switched off with INFRAHUB_API_BACKPRESSURE_ENABLED=false. The bundled Grafana dashboards now chart both, so an operator can see what is being shed and why.
Sort object lists the way you need them
Object lists can now be sorted from the UI, using the backend ordering support added in 1.10. There are two ways in, and they stay in agreement because both read from the page URL.
- From a column header. Clicking a header opens a menu with "Sort ascending", "Sort descending" and "Filter". Columns pointing at a single related object offer a "Sort by" entry listing that object's sortable attributes. The header shows the active direction, and choosing the active direction again returns to the default order. Per-column filtering, previously opened by clicking the header directly, is now the "Filter" item in this menu.
- From the toolbar. A Sort control beside the existing Filter control opens a list of sort entries you can add, reorder by dragging, and remove. Dragging changes which field takes precedence. The control shows a count when a sort is active, and reveals the schema's own default ordering when none is set.
The sort lives in the URL, so a sorted view can be bookmarked and shared. Sorting runs on the server, so large lists are not slower to sort. This works in object lists, in the IPAM IP address and prefix lists, and — new in b1 — in the proposed changes list, which now defaults to newest created first.
Set the date and time format you want to read
Dates in the UI followed the browser's locale, and an organisation had no way to set a house format.
Each user can now choose their own date format and timezone from a Preferences card on their account Profile tab, and the choice follows them to any device. Administrators holding the new manage_global_preferences permission set organisation-wide defaults on a Global preferences tab. A field a user leaves unset inherits the organisation default, and falls back to the browser's setting when no default exists. Personal preferences are private: reading or writing them only ever touches your own record.
Delete a repository without emptying it by hand first
Deleting a repository used to fail, because the objects it imports and generates — transforms, checks, GraphQL queries, Generators, and the artifact definitions, artifacts, Generator instances and validators below them — are attached by mandatory relationships that Infrahub correctly refuses to break. The only way through was to delete the whole tree by hand, leaf first.
Deleting a repository now cascades to the objects it owns. Infrahub still refuses the deletion, with a clear error, if a cascaded object is required by something outside the repository, so an incomplete cascade cannot silently leave orphaned data behind. Note that this now also removes validators attached to Proposed Changes and the artifacts and Generator instances they produced, which is the intended ownership relationship.
Merge branches that reordered the same list
Attribute schemas gain an ordered flag. Setting it to false on a List or JSON-array attribute means reordering that attribute's elements is no longer reported as a conflict during a merge or rebase — while adding or removing an element still is.
The built-in enum, dropdown choices, used_by and restricted_namespaces attributes now use this. That resolves a case where two branches ended up holding the same set of dropdown choices in a different order and the branch still could not be rebased.
Store a bare IP address
New in b1.
A lot of infrastructure data is a plain IP address with no subnet context: DNS A and AAAA records, NTP, syslog and SNMP destinations, anycast VIPs, ACL entries, monitoring targets, load balancer pool members. Until now the only options were IPHost, which silently rewrites 10.0.0.1 as 10.0.0.1/32, or Text, which gives up validation entirely.
The new IPAddress attribute kind stores the address and nothing else. A value carrying a prefix length or netmask is rejected rather than accepted and normalized, and IPv6 values are normalized to their compressed lowercase form. It maps to Python's ipaddress.ip_address, and the SDK gains the matching IPAddress / IPAddressOptional protocol types. The form field drops the prefix-length selector.
| Kind | Stores | Example |
|---|---|---|
IPAddress (new)
| a bare address | 192.0.2.1
|
IPHost
| address + prefix length | 192.0.2.1/24
|
IPNetwork
| network + prefix length | 192.0.2.0/24
|
One limitation to know about: IPAddress values sort lexically, not numerically. This is an attribute kind for IP data on your own schema nodes — it is unrelated to Infrahub's IPAM features, which use their own built-in nodes with pools and hierarchy. The full list of attribute kinds is in the schema attribute reference.
Related to this, changing an attribute's kind is now refused when the values already stored are not in the canonical form of the new kind — see Breaking changes.
Know what a schema load did not apply
New in b1 (the validation contract itself landed before b0; the reporting is new).
POST /api/schema/load accepted your whole payload and then quietly dropped every field you were not allowed to set. A misspelled key therefore produced a schema subtly different from the one you wrote, with nothing to tell you. Every submitted node, generic and extension is now validated against a user-facing write contract, and the endpoint says what it did.
-
Invalid values are rejected with the field path, naming the field and the value:
nodes[0].relationships[0].cardinality: Input should be 'one' or 'many' (received: 'several') -
Attribute
parametersbelonging to a different kind are rejected —start_rangeon aNumberattribute, for example, which earlier versions accepted and then discarded, so the setting silently had no effect. -
Fields Infrahub computes and owns are accepted with a warning, one per distinct field, naming every kind and element that carried it. These are
inherited,used_by,hierarchy, a relationship'shierarchical, a node's derivedkindandhash, and the bookkeeping a schema dumped from Infrahub carries on nested blocks such asparameters. The submitted value is ignored, so reading a schema back from Infrahub, editing it and loading it again keeps working:'inherited' is a read-only field, the submitted value is ignored [InfraDevice.name, InfraDevice.interfaces] -
A name the contract does not recognize at all — a typo, or a field removed in a newer version — is rejected, now with the same field-level path.
infrahubctl schema load and infrahubctl schema check print these warnings, and infrahubctl validate schema reports them offline. The warnings are returned even when the submitted schema matches the one already loaded, so a deprecation or read-only-field warning is no longer reported only on the first load.
The write contract is published as a committed model in the Python SDK (infrahub_sdk.schema.generated.write); validate_schema() reproduces the server verdict, warnings included, so a payload can be checked before it is submitted, and client.schema.validate() now raises ValueError rather than a pydantic ValidationError.
GET /api/schema returns the same response body as before. Its OpenAPI component schemas were renamed — see Breaking changes.
Allocate from an IP pool with a specific prefix length
New in b1.
Allocating from an IP address or IP prefix pool always used the pool's own default prefix length. You can now request a specific one at allocation time, so a single pool can serve, for example, both /31 point-to-point links and /29 management segments.
Asking for a prefix length that conflicts with an existing reservation now returns a clear error instead of silently handing back the existing allocation at its original size.
Richer anonymous telemetry
New in b1.
The daily anonymous telemetry payload gains adoption and activity signals, to show how Infrahub is actually used rather than only how large the database is:
- Adoption — active accounts and account groups, the number of open non-system branches, and two branch- and temporal-correct node counts computed the way the product counts nodes (
database.node_count.corenodefor all managed nodes,database.node_count.userfor user-defined namespaces), distinct from the existing raw vertex total. - Activity — a new
activity_24hobject summarizing the previous full UTC day: logins and unique logins, validation checks started, passed and failed, artifacts created and updated, branches created, merged and deleted, and webhook deliveries that succeeded or failed.
Every field is gathered independently, so one failing source reports null for its own field and the rest of the payload is still sent; a source with nothing to count reports 0. All additions are backwards-compatible and the payload_format identifier is advanced accordingly. Telemetry remains anonymous and can be disabled as before.
Minor changes
- Removing a relationship from a schema now closes the underlying relationship data instead of leaving it active but unreachable in the graph. Previously the data was orphaned and stale peers would resurface if a relationship with the same identifier was added again later.
- Markdown artifacts render Mermaid diagrams from
```mermaidcode blocks. - Hierarchical
parentandchildrenrelationships now show the related kind's label — "Region" or "Site", rather than "Parent" and "Children" — in the detail view, tabs, table headers, filters, the sort picker and create and edit forms. - Adding a child to a hierarchical object now pre-fills the parent field with the object you started from.
- The branch "Sync with Git" flag now carries an explanation clarifying that it controls whether an Infrahub-created branch is propagated to Git, and does not indicate whether the branch came from Git.
- A branch's details page now tells you whether you are working on that branch, and offers a one-click switch when you are not.
- Relationship selectors in object forms now honor the
common_parentschema property, so the options are limited to peers sharing the parent already chosen in the form rather than listing every peer. - The web UI now identifies the default branch by its flag rather than by the name
main, so an instance whose default branch is named something else behaves correctly. - Git repository synchronization no longer halts when a branch that was already merged still exists on the remote.
- Collecting the changed-field summary of a diff is now paged instead of aggregated in one query, so a very large diff no longer risks exhausting the database heap.
- GraphQL queries that request only the
idof a cardinality-one relationship's peer no longer hydrate the full peer node, cutting database work on relationship-heavy queries. - Node creation no longer fails when a Jinja2 computed attribute formats a value taken from a number pool; the attribute renders once the pool value is allocated.
- The Infrahub container image is significantly smaller: the build toolchain now lives in a build stage excluded from the runtime image, and
numpyandpyarroware no longer installed by default (pyarrowremains available through theobject-transferextra forinfrahubctl object load). - The frontend GraphQL transport moved from
@apollo/clientto the lighter@urql/core, reducing the JavaScript bundle. Apollo was used transport-only, and all request behavior is preserved. - The documentation gains hardware requirements guidance for local development and evaluation sizing, and states the single-core Neo4j limitation of the Community edition.
- The deprecated
infrahub git-agentcommand has been removed. It was replaced by the task worker some time ago.
Breaking changes
Read this section before upgrading.
Artifacts are deleted when their target leaves the target group
Artifact generation now deletes artifacts whose target object is no longer a member of the artifact definition's target group. Previously those artifacts were left in place indefinitely as stale copies — for example after merging a branch that removed the target from the group.
If any workflow reads those leftover artifacts, capture their content before the target is removed from the group. The deletion happens on the next full generation pass over the definition: after a merge, after a definition update, or on a call to the generate endpoint with no node filter.
A related fix limits this cleanup to passes that examined every member, so a generation run narrowed to specific members no longer deletes artifacts it never looked at.
POST /api/schema/load rejects two payload shapes it used to accept
Both were accepted and then silently discarded, so a schema that loaded "successfully" was not the schema you wrote:
- A field name the write contract does not recognize — a typo, or a field removed in a newer version.
- An attribute
parameterbelonging to a different attributekind— for examplestart_rangeon aNumberattribute.
Fields Infrahub owns and computes (inherited, used_by, hierarchy, hierarchical, a node's derived kind and hash, and the bookkeeping on nested blocks) are still accepted, now with a warning, so a schema read back from Infrahub still loads. Check your schema files against the new contract with infrahubctl validate schema before upgrading.
OpenAPI component schemas for GET /api/schema were renamed
APINodeSchema, APIGenericSchema, APIProfileSchema and APITemplateSchema are now NodeSchemaRead, GenericSchemaRead, ProfileSchemaRead and TemplateSchemaRead. The response body itself is unchanged; only clients generating types from openapi.json need to update those type names.
Changing an attribute kind now requires canonical values
Changing an attribute's kind is refused when the values already stored are not in the canonical form of the new kind. This affects conversions into IPHost, IPNetwork, IPAddress and MacAddress — converting a Text attribute holding aa-bb-cc-dd-ee-ff to MacAddress is now refused, because the canonical form is AA:BB:CC:DD:EE:FF. Normalize the values first, then change the kind.
Full changelog
Entries marked (new in b1) were added after v1.11.0b0.
Added
- (new in b1) Added a new
IPAddressattribute kind that stores a bare IP address. UnlikeIPHost, which normalizes192.0.2.1to192.0.2.1/32, anIPAddressvalue must not carry a prefix length or netmask, and any value that does is rejected. IPv6 values are normalized to their compressed lowercase form. Note that values sort lexically rather than numerically. - (new in b1) Added the ability to request a specific prefix length when allocating from an IP address or IP prefix pool, instead of always using the pool's default.
- (new in b1) The daily anonymous telemetry payload now reports additional adoption and activity signals. New fields cover active accounts and account groups (
accounts.active,accounts.groups), the count of open non-system branches (branches.active), and two branch- and temporal-correct node counts computed the same way the product counts nodes, distinct from the existing raw vertex total:database.node_count.corenode(all managed nodes) anddatabase.node_count.user(user/business nodes in user-defined namespaces, excluding internal and built-in objects). A newactivity_24hobject summarizes what happened over the previous full UTC calendar day: logins and unique logins, validation checks started/passed/failed, artifacts created/updated, branches created/merged/deleted, and webhook deliveries that succeeded or failed. All changes are additive and backwards-compatible. Each field is reported independently, so a single failing source yieldsnullfor that field while the rest of the payload is still gathered and sent; a source that succeeds with nothing to count reports0. Thepayload_formatidentifier is advanced to reflect the new payload version. - (new in b1) A branch's details page now says whether you're working on that branch, and lets you switch to it. When the page you're viewing isn't the branch you're working on, a notice explains that edits won't land there and offers a one-click switch.
- (new in b1) Added a Sort picker to the proposed changes list to order it by any sortable field, including creation and update dates. The list now defaults to newest created first. (#9915)
- Added a
selective_execution_after_mergesetting (envINFRAHUB_SELECTIVE_EXECUTION_AFTER_MERGE) that narrows post-merge regeneration to the artifact and Generator definitions the merge actually affected, instead of regenerating every definition for every member. When enabled, a merge captures its diff and dispatches only the definitions whose query, definition, or code inputs changed, and only for the affected members. Every uncertain signal, such as a disabled flag, a missing or unreadable diff summary, a definition with no computed fingerprint, or an incomplete dependency closure, falls back to full regeneration, so no affected artifact or Generator can be left stale. On a direct merge that runs a Generator, the artifacts consuming that Generator's output are regenerated after the Generator completes. The setting is enabled by default. CoreGraphQLQuery,CoreTransformation,CoreArtifactDefinition, andCoreGeneratorDefinitionnow carry an optional, branch-awarefingerprintattribute. It holds a content hash of the definition's inputs, laying the groundwork for change-driven regeneration. A null value means the node predates the feature or has not been re-imported since; nothing is backfilled.- Webhook deliveries are now first-class, observable tasks. Each delivery runs as its own task, retries automatically on failure, reports a classified failure reason with a remediation hint instead of a raw stack trace for expected delivery errors, records the request it sent and the response it received, and can be retried or cancelled by an operator.
- Writes to the default branch and to the source branch are now blocked for the full duration of a branch merge, and a new merge or rebase is refused while a merge is in progress. Blocked writes receive a transient message asking the caller to retry shortly, and the protection is lifted automatically once the merge completes (or is rolled back).
- Added the
infrahub recover mergeCLI command to recover from a failed branch merge. It rolls back the partial merge on the default branch, resets the branch and any associated proposed change toOPEN, and lifts the write protection so the default branch is writable again. The command is operator-confirmed (skip the prompt with--yes) and idempotent: a run with nothing to recover reports so and makes no changes. While a branch is in the failed state, all mutations against it, including its deletion, are refused until recovery has run. - Branch-status write rejections now carry structured GraphQL error codes in the error
extensions:BRANCH_ALREADY_MERGED,BRANCH_NEEDS_REBASE, andMERGE_IN_PROGRESS.MERGE_IN_PROGRESS(HTTP status 423) signals a transient block while a merge is running and includes the branch being written and the branch being merged, so API and SDK clients can retry on the code instead of matching the message text. - Priority-aware API backpressure. The API now accepts an optional
X-Priorityrequest header (high,medium, orlow; defaults tomediumwhen absent or invalid). Under sustained overload the admission layer sheds lower-priority traffic first, returning429 Too Many Requestswith aRetry-Afterheader while the request handler never runs, so interactivehightraffic keeps flowing. Eight newinfrahub_admission_*Prometheus metric families are exposed on/metricsfor observability. The layer is disabled-inert on a normally-loaded deployment and can be turned off entirely withINFRAHUB_API_BACKPRESSURE_ENABLED=false. - The frontend now declares request priority on every API call via the
X-Priorityheader, sendinghighby default andlowfor opt-in background traffic. The API CORS allowed-headers list now includesx-priorityso the header is accepted from the browser. - Added a database-stress signal derived from the reference permission query and used it to make API load shedding progressively tiered. The signal tracks the query's all-time fastest measured execution time (the "floor", timed over read executions of the query only) and, over a rolling window, how much slower the database currently is; new Prometheus gauges expose it (
infrahub_db_reference_query_floor_seconds,infrahub_db_reference_query_window_min_seconds,infrahub_db_reference_query_stress_ratio_median). Database stress is now an independent shed trigger in the admission layer alongside CoDel: as a class's stress ratio climbs past its trigger, a growing fraction of that class is shed (20% within 1–2x the trigger, 50% within 2–5x, 80% at or beyond 5x) rather than the whole class at once. The triggers are tiered per class (low 5x, medium 20x, high 100x), so low-priority traffic sheds first, then medium, and high-priority (interactive) traffic is protected until the database is under extreme load. Stress-driven sheds are reported under a distinctreason="stress"label oninfrahub_admission_rejected_total. NewINFRAHUB_API_BACKPRESSURE_*settings tune the window, warm-up sample count, per-class stress thresholds, and per-class backstop caps. - Added user and global preferences as internal
StandardNodeobjects (not schema nodes). A singlePreferencemodel holds both layers: one row per user stores that user's overrides, and a separate row stores the organisation-wide defaults (date_format,timezone). Rows are lazily created on first write; a missing row means nothing is set. TheInfrahubEffectivePreferencesGraphQL query returns the merged effective values for the calling user (user override → global default → the client's built-in default), each with the layer it was resolved from. User preferences are private by construction — there is no generic query, and the custom query and mutation only ever touch the caller's own row. - Added a personal Preferences card to the account Profile tab, where each user picks their own date format (from curated presets) and timezone (searchable IANA list). A field left unset inherits the organisation default, or the browser default when none is set.
- Added a Global preferences tab (
/profile/global-preferences) to account settings, visible only to users holding themanage_global_preferencespermission, where administrators set the organisation-wide default date format and timezone that apply to every user without a personal override. - Added the
manage_global_preferencesglobal permission. TheInfrahubSetPreferencesmutation enforces it whenscopeisGLOBAL, and theInfrahubGlobalPreferencesquery enforces it before reading the organisation-wide row (super admins implicitly pass). Writing your own preferences (scope: USER) or reading them (InfrahubUserPreferences) needs no permission — those paths only ever touch the calling account's own row. - Removing a relationship from a schema now closes the relationship data using that schema instead of leaving them active, but unreachable, in the graph. Previously the data was orphaned, which would resurface stale peers if a relationship with the same identifier was re-added later. (#2474)
- Markdown artifacts now render Mermaid diagrams from ```mermaid code blocks.
- Added an
inputstyle variant to the Button component and adopted it for selector triggers (sort, filter, user preferences, number pool) so they match the styling of form inputs.
Changed
-
Breaking: artifact generation now deletes artifacts whose target is no longer a member of the artifact definition's target group. Previously such artifacts were left behind indefinitely as stale copies, for example after merging a branch or proposed change that removed the target from the group. Any workflow that relied on reading those leftover artifacts must capture their content before the target is removed: the next full generation pass over the definition (after a merge, a definition update, or a call to the generate endpoint without a node filter) deletes them. (#9790)
-
Breaking:
POST /api/schema/loadnow validates every submitted node, generic, and extension against a user-facing write contract, and reports the fields it does not apply instead of ignoring them. Constrained fields (for example an attributekindor a relationshipcardinality) set outside their allowed values, and out-of-range values, are rejected with a field-level error naming the field and the invalid value. (new in b1) Attributeparametersbelonging to a different attributekindare rejected too — for examplestart_rangeon aNumberattribute, which earlier versions accepted and then discarded, so the setting silently had no effect. (new in b1) Fields Infrahub computes and owns are accepted and reported as a warning, one per distinct field, naming every kind and element that carried it:inherited,used_by,hierarchy, a relationship'shierarchical, a node's derivedkindandhash, and the bookkeeping a schema dumped from Infrahub carries on nested blocks such asparameters. The submitted value is ignored, so reading a schema back from Infrahub, editing it, and loading it again keeps working.infrahubctl schema loadandinfrahubctl schema checkprint these warnings;infrahubctl validate schemareports them offline. A field the contract does not recognize at all — a typo, or a field removed in a newer version — is rejected, now with the same field-level path.GET /api/schemareturns the same response body as before and still includes read-only fields such asinheritedandused_by; its OpenAPI component schemas are now named after the generated read models —NodeSchemaRead,GenericSchemaRead,ProfileSchemaRead, andTemplateSchemaReadin place ofAPINodeSchema,APIGenericSchema,APIProfileSchema, andAPITemplateSchema— so a client generating types fromopenapi.jsonneeds to update those type names. The write contract is published as a committed model in the Python SDK (infrahub_sdk.schema.generated.write); the SDKvalidate_schema()helper reproduces the server verdict offline, including the warnings, so a payload can be checked before submission.client.schema.validate()reaches the same verdict and raisesValueErrorrather than a pydanticValidationError. -
Breaking (new in b1): changing an attribute's kind is now rejected when the existing values are not already stored in the canonical form of the new kind. This affects conversions into
IPHost,IPNetwork,IPAddressandMacAddress. For example converting aTextattribute holdingaa-bb-cc-dd-ee-fftoMacAddressis now refused, because the canonical form isAA:BB:CC:DD:EE:FF. Normalize the values first, then change the kind. -
(new in b1) Allocating from an IP address or prefix pool with a conflicting prefix length now returns a clear error instead of silently reusing the existing reservation.
-
(new in b1) Improved the performance of GraphQL queries that only request the
idof a cardinality-one relationship's peer. When no properties, metadata, or additional node fields are requested, the resolver now returns the peer ID already loaded on the parent instead of hydrating a full peer node, reducing database work on relationship-heavy queries. -
(new in b1) Collecting the altered field names of a diff is now paged over changed nodes instead of aggregated in a single query, so a very large diff no longer risks exhausting the database heap.
-
When a proposed change includes commits to a linked repository, Infrahub now re-runs only the Generators whose source, GraphQL query, or definition was actually affected by the change, instead of running every Generator in the repository on any file change. This extends the precise regeneration already applied to artifacts. Each run or skip decision is recorded in the proposed change's task log, naming the file, query, or definition field that triggered it. Read-only repositories participate on the same terms, even on branches where
sync_with_gitis disabled. Generators imported before this change keep working unchanged and adopt the precise behavior automatically on their next import; until then they conservatively re-run on any file change in their repository.For dependencies Infrahub cannot detect automatically, such as helper modules imported at runtime from a sibling package, you can declare extra files with the optional
watch:key ongenerator_definitionsentries in.infrahub.yml. Changes to a declared file or directory then re-run that Generator's instances. -
When a Git commit changes a Python transform, Infrahub now recomputes only the computed attributes whose transform actually changed, instead of recomputing every transform-based computed attribute on any commit. The first time a transform is imported it still recomputes its attributes once, so the initial import keeps a one-time recompute cost.
-
Merging or rebasing a branch now runs a single coalesced recompute for computed attributes, display labels, and human-friendly ids instead of one recompute job per changed node. The work after a merge or rebase scales with the number of affected derived values rather than the changed-node count, so a large merge or rebase returns the instance to normal much faster while producing the same derived values.
-
Recomputing a Python-transform computed attribute for a batch of nodes now initializes the transform's git repository once for the whole batch instead of once per node, and persists the results in a single bulk write instead of a GraphQL mutation per node. A node whose recomputed value is unchanged is now skipped, so it neither writes nor triggers a further recompute. This shortens the trailing recompute after a merge or rebase that affects many nodes of the same kind (for example a device-type change that refreshes every device's computed description).
-
Tasks a user is blocked on now run at high priority: branch mutations (create, merge, rebase, delete, validate), proposed-change merge, and generator-definition runs dispatch at high priority when waiting for completion, as do the schema load/check, transform rendering, and on-demand artifact generation endpoints. Profile refresh tasks now run at low priority alongside the other derived-value tasks, and post-merge follow-ups (proposed-change cancellation, automatic branch deletion, artifact and Generator regeneration) run at low priority instead of inheriting high priority from the merge, with IPAM reconciliation kept at medium.
-
Column headers in object lists and IPAM IP address/prefix lists now open a menu to sort the list directly from the column: sortable attribute columns offer "Sort ascending" and "Sort descending", and to-one relationship columns offer a "Sort by" entry listing the related object's sortable attributes. The header shows a direction indicator for the active sort, selecting the active direction again restores the default order, and the sort stays in sync with the toolbar Sort control and the page URL. Per-column filtering, previously opened by clicking the header, is now the "Filter" item in this menu.
-
Hierarchical
parentandchildrenrelationships now display the related kind's label (for example "Region" or "Site") instead of the generic "Parent"/"Children" everywhere they appear — the object detail view, tabs, table column headers, filters, the sort picker, and create/edit forms. -
Added an explanation to the branch "Sync with Git" flag (in both the branch details view and the create-branch form) clarifying that it controls whether an Infrahub-created branch is propagated to Git, and does not indicate whether the branch originated from Git. (#9883)
-
Flow runs that stop sending heartbeats are marked as crashed only after a longer grace period, so a run waiting out an automatic retry is no longer prematurely marked as crashed.
-
Upgraded Python to 3.14 (from 3.13). Upgraded Neo4j to 2026.05.0 (from 2025.10.1).
Fixed
- (new in b1) Python computed attributes no longer recompute on updates to nodes their query never reads. When a query followed a relationship whose peer is a generic, a trigger was created for every member kind of that generic, including kinds no field was read from. Those triggers carried no field filter, so any update to such a node started a recompute, and the values that recompute wrote could match the same triggers again and cascade.
- (new in b1) Merging or rebasing a branch that deletes a node now refreshes the derived values of the nodes that read the deleted node across a relationship. Their computed attributes, display labels, and human-friendly ids no longer keep naming the deleted node.
- (new in b1) Fixed issue where Jinja2 Transformations were always marked as being updated during repository imports even though there were no changes. (#3094)
- (new in b1) Fixed node creation failing when a Jinja2 computed attribute formatted a value sourced from a number pool; the computed attribute now renders once the pool value has been allocated. (#7836)
- (new in b1)
POST /api/schema/loadnow returns the warnings it collected even when the submitted schema matches the one already loaded. Previously the response for an unchanged schema omitted them, so a deprecation or read-only-field warning went unreported on every load after the first. - (new in b1) Fixed git repository synchronization halting when a branch that had been merged still existed on the remote. (#9931)
- (new in b1) Fixed relationship selectors in object forms not honoring the
common_parentschema property. The options are now filtered to peers that share the same parent as the value picked for the referenced relationship in the same form, instead of listing every peer. Changing that parent clears a now-invalid selection, and the inline "Add new" form pre-fills the parent when one is already selected so a created peer stays valid. - (new in b1) The web UI now identifies the default branch by its flag instead of by the name
main, so an instance whose default branch carries another name resolves it correctly. - (new in b1) Fixed concurrent GraphQL calls being folded into a single in-flight request, so each call now issues its own request.
- Branches containing only data changes no longer trigger every validator across every kind in the schema. Schema diffs between identical branches now come back empty, and constraint validation is scoped to the kinds with changed data or schemas. (#2592)
- Schema constraint validation triggered by a data change now runs only the node-level constraints (uniqueness and hierarchy) whose specific field or path was actually modified, instead of every node-level constraint defined on the changed kind. A change to an attribute that participates in no uniqueness constraint no longer re-validates the kind's uniqueness, reducing the work performed during proposed-change checks, merges, and rebase operations.
- Scope computed-attribute recompute to the schema elements a change actually touches. Previously any schema change recomputed every computed attribute on the branch (for Python-transform attributes, one recompute job per object of its kind), regardless of relevance. A computed attribute is now recomputed only when the change affects a schema element its value depends on — including elements reached through relationships — while changes whose impact cannot be determined still fall back to a full recompute so nothing goes stale. (#9415)
- Added an
orderedflag to attribute schemas. When set tofalseon aListorJSON-array attribute, reordering its elements is no longer reported as a conflict during branch merge and rebase (element counts still matter, so adding or removing an element is still a conflict). The built-in schemaenum, dropdownchoices, genericused_by, andrestricted_namespacesattributes now use this so that two branches reordering the same list can be merged without a manual conflict resolution. (#9764) - Artifact generation no longer deletes artifacts that a narrowed run never examined. A run limited to specific members was treated as having evaluated the whole target group, so the stale-artifact cleanup could remove artifacts whose target was still a member. The cleanup now runs only for a pass that examined every member.
- Artifact and Generator regeneration during a proposed change no longer fails when a definition references a repository that has no changes in the branch diff; the missing repository is now skipped instead of aborting the task.
- Selective regeneration no longer skips an artifact or Generator when the change lands on a node the definition's query reads through a relationship. Such a node is not tracked as a member of the query's target group, so narrowing previously resolved it to no subscriber at all and left the output stale; these changes now regenerate every member of the definition instead. This also covers a kind the query reads both directly and through a relationship, which was mistaken for a directly-targeted kind.
- Selective regeneration after a merge now narrows Generator instances to the members actually impacted, instead of rerunning every instance in the group.
- Fixed display labels and human-friendly ids that read across a relationship not refreshing when they were recomputed by their own id. Resolving a cross-node template overwrote the cached own-id filter in place, so a later self recompute queried with the relationship filter, matched no node, and left the stored value stale.
- Rolling back a failed branch merge now fully restores
updated_at/updated_bymetadata. Objects that first landed on the default branch as part of the failed merge have their merge-time stamps cleared instead of keeping the failed merge timestamp, and a recovery that is interrupted partway through can be re-run without leaving merge-time metadata or orphaned graph records behind. - Recovering from a failed branch merge now restores
updated_at/updated_bymetadata for objects, attributes, and relationships affected by a schema migration, such as changing the inheritance of a schema or adding a new attribute. - Webhook task runs are now discoverable from the webhook related tasks panel.
- A TLS certificate verification failure when Infrahub connects to an external HTTPS endpoint (such as a webhook target or an SSO provider) is now reported as a TLS error instead of a generic connection error.
- Fixed some GraphQL requests returning an unexpected HTTP 500 error instead of a proper node-not-found (404) response. (#9926)
- When adding a child to a hierarchical object (for example a Country under a Continent), the creation form now pre-fills the parent field with that object, so the parent no longer has to be selected by hand.
- Fixed the focus ring on text inputs being clipped on the left and right edges when an object form is opened inside a sheet (for example the "Add object" form). The form no longer adds its own redundant scroll container, so the ring is drawn in full.
Removed
- Removed the
infrahub git-agentcommand line utility, which was deprecated long ago and replaced by the task worker. (#5584)
Housekeeping
- (new in b1) Replaced the frontend GraphQL transport (
@apollo/client) with the lighter@urql/core, reducing the JavaScript bundle size. Apollo was used transport-only (no hooks, no cache); all request behavior — auth, request priority, error routing, token refresh, and file uploads — is preserved. No user-facing behavior changes. - (new in b1) Upgraded FastAPI to
0.141.1and the OpenTelemetry instrumentation and exporter stack to the0.65b0/1.44.0line. This unblocks native FastAPI frontend serving and picks up the OpenTelemetry instrumentation fix required for FastAPI 0.137 and later. (#10101) - (new in b1) Extended the bundled Grafana dashboards with Admission Controller and DB Stress Signal sections covering the new backpressure metrics, and fixed several Neo4j and Prefect panel definitions.
- (new in b1) Documented merge failure and recovery (
infrahub recover merge, the write protection and the operator-facing errors), added local development and evaluation sizing to the hardware requirements page, and documented the single-core Neo4j limitation of the Community edition. - Significantly reduced the size of the Infrahub container image: the build toolchain now lives in a dedicated build stage that is excluded from the runtime image, and the
numpyandpyarrowdependencies are no longer installed by default (pyarrowremains available via theobject-transferextra forinfrahubctl object load). - Restructured the frontend tooling around a pnpm workspace: all frontend packages (
app,packages/ui, and theschema-visualizergit submodule) are now members of a single pnpm workspace atfrontend/with one shared lock file; cross-package dependency versions are managed through the pnpmcatalog:; install-time build scripts are disallowed and Playwright versions are pinned viaoverrides; and the node stages ofdevelopment/Dockerfilewere restructured with BuildKit cache mounts for parallel builds and better layer-cache reuse. - Introduced a shared
isRelationshipSchematype guard in the frontend schema entity and routed the previously inline"peer" in fieldSchemafield-schema checks through it, so attribute/relationship discrimination now lives in one tested place instead of being duplicated across form, table, filter and detail components. - Made the Neo4j Bolt connector thread-pool ceiling tunable in the testcontainers stacks via the
INFRAHUB_TESTING_DB_BOLT_THREAD_POOL_MAX_SIZEenvironment variable (defaulting to Neo4j's own400, so normal test runs are unaffected). Heavy dataset/performance runs can raise it to avoidNeo.TransientError.Request.NoThreadsAvailablewhen a wide computed-attribute recompute fan-out saturates the pool.