Skip to content

Error Codes

When Zhang loads a ledger, it reports the problems it finds as errors. The ledger still loads: the errors page of the web UI lists them, with the directive that caused each one, and so do GET /api/errors and the #errors query table. They come by file, then by position in the file, and the errors page shows where each one is as its file and the byte offsets of the directive. Each error has a code, listed below with the message the errors page shows for it. An error may carry metas, such as the transaction_amount of a posting; a number in a meta is written in plain notation, with all its digits and decimal places (-0.0000001, never -1E-7), as in the rest of the API.

Some problems stop the ledger from loading instead; they have no code. See When the ledger does not load.

Transaction is Unbalanced

The postings of a transaction do not add up to zero in some commodity. As in Beancount, each commodity must balance on its own. A posting contributes its weight:

  • its units, for a plain posting;
  • its units converted at the price, for 10 USD @ 7 CNY (70 CNY) or 10 USD @@ 70 CNY;
  • its units at cost, for a posting with a cost. For {}, the cost of the lots the posting reduces, so -15 USD {} against lots 10 USD {10 CNY} and 10 USD {11 CNY} (FIFO) weighs -155 CNY.

The sum of each commodity is rounded to the commodity’s precision with its rounding before it is compared with zero, so with precision 2 a difference of 0.004 passes. If a weight commodity is not defined, CommodityDoesNotDefine is reported instead.

2024-01-01 open Assets:Cash
2024-01-01 open Expenses:Food
2024-01-02 * "Cafe" "lunch"
Assets:Cash -10 CNY
Expenses:Food 12 CNY

The transaction is still booked as written. Fix: correct the amounts, or leave out the amount of one posting so Zhang infers it. See How a transaction balances.

Cannot infer the trade amount of the transaction

One posting leaves out its amount, and there is nothing to infer it from: the other postings have no amounts, or they balance in several commodities.

This also reports a cost that cannot be resolved: a purchase with both its cost and cash amount omitted, multiple missing costs, a cost requiring division by zero or a negative cost, or a {} reduction with no matching cost lots. An unmatched reduction also reports NoEnoughCommodityLot. The entire transaction is rejected and its previous holdings are retained.

2024-01-01 open Assets:Cash
2024-01-01 open Expenses:Food
2024-01-02 * "Cafe" "lunch"
Assets:Cash

If the other postings already balance in a single commodity, the posting without an amount gets zero of it (a sale at cost with an implicit gain books 0 CNY), so the journal still shows the posting you wrote; Beancount drops such a posting instead.

The inferred amount is exact: amounts, costs, prices and their products are never rounded. Only a cost that Zhang has to divide, such as a total cost {{1000 USD}} spread over 3 units (333.3333333333333333333333333 USD each, to 28 significant digits as in Beancount), leaves the remainder of a division in the amount; such an amount is rounded, with the commodity’s rounding, at the larger of the commodity’s precision and the most decimals written in the transaction in that commodity. Selling all 3 units then gives back exactly 1000 USD.

The transaction is not booked. Fix: write the amounts of the other postings, or the amount of this one.

Transaction has more than one implicit posting unit

More than one posting leaves out its amount. As in Beancount, only one posting per transaction may do so.

2024-01-01 open Assets:Cash
2024-01-01 open Assets:Card
2024-01-01 open Expenses:Food
2024-01-02 * "Cafe" "lunch"
Expenses:Food 10 CNY
Assets:Cash
Assets:Card

The transaction is not booked. Fix: write the amounts of all postings but one.

TransactionExplicitPostingHaveMultipleCommodity

Section titled “TransactionExplicitPostingHaveMultipleCommodity”

Explicit postings of the transaction use multiple commodities

One posting leaves out its amount, and the weights of the other postings leave more than one commodity unbalanced, so Zhang cannot tell which commodity the missing amount is in.

2024-01-01 commodity USD
2024-01-01 open Assets:Cash
2024-01-01 open Assets:Card
2024-01-01 open Expenses:Food
2024-01-02 * "Cafe" "lunch"
Expenses:Food 10 CNY
Assets:Card -5 USD
Assets:Cash

The posting without an amount is inferred from the weights of the other postings, after their lots are matched. An unmatched {} reduction instead reports TransactionCannotInferTradeAmount after NoEnoughCommodityLot, because its cost cannot be resolved.

The transaction is not booked. Fix: write every amount, or convert with a price (@) so the weights are in one commodity.

Account does not pass the balance check

A balance assertion does not match: the account and its sub-accounts hold a different amount of the commodity, by more than the assertion’s ~ tolerance, or at all without one. A balance … with pad also fails when its padding cannot bring the total to the asserted amount.

2024-01-01 open Assets:Cash
2024-01-01 open Equity:Opening-Balances
2024-01-01 * "Opening balance"
Assets:Cash 100 CNY
Equity:Opening-Balances
2024-01-02 balance Assets:Cash 500 CNY

The error’s account_name meta names the asserted account. A failing assertion only reports this error: it changes no balance, and the account keeps the sum of its postings everywhere. The journal shows the assertion with the asserted amount and the actual balance.

Fix: find the missing or wrong transaction and correct it. Remember that an assertion without a time is checked before the transactions of its own day. To set the balance on purpose, use balance … with pad.

Pad is not used by any later balance assertion of its account

A pad padded nothing: no later balance assertion of its account needed it. Beancount reports the same error (“Unused Pad entry”). A pad serves the first balance of its account in each commodity on a later day than the pad, until the account’s next pad; it is unused when every such assertion already holds, when no assertion of the account follows it on a later day, or when another pad of the account replaces it first.

; Assets:Checking holds 100 USD
2024-01-01 pad Assets:Checking Equity:Opening-Balances
2024-01-02 balance Assets:Checking 100 USD

Fix: remove the pad, or move it before the assertion it is meant to serve. A balance on the day of the pad comes before it and is not padded, whatever their times.

Pad of a commodity the account holds at cost: the padding is booked without a cost

A pad would pad a commodity that its account, or one of its sub-accounts, holds at cost, in lots with a cost such as shares bought {100 USD}. The padding is still booked, without a cost, and the error is reported on the balance assertion it serves, as Beancount reports “Attempt to pad an entry with cost”. Zhang reports it once for the assertion; Beancount reports it once for each lot held at cost.

2024-01-02 * "Buy"
Assets:Broker:Stock 10 AAPL {100 USD}
Assets:Broker:Cash -1000 USD
2024-01-03 pad Assets:Broker:Stock Equity:Opening-Balances
2024-01-04 balance Assets:Broker:Stock 15 AAPL

Fix: book the missing units with a transaction that gives their cost, instead of padding them.

Balance checked at the start of its date: its time is ignored, as beancount ignores it

A notice, not an error of the ledger: it is listed with the errors, but the balance passes or fails on its own. It is reported, once, on a balance of a Beancount ledger whose check means something else than in earlier versions of Zhang. Zhang checks a balance of a Beancount ledger at the start of its date, before every transaction of that day, as Beancount does, and ignores its time metadata. Earlier versions read a time written H:M:S and checked the balance at that time, after the transactions of the day before it. The notice is given when those transactions changed what the account and its sub-accounts hold in the balance’s commodity: the balance now checks a different amount, and a pad serving it pads a different amount. A time like 09:30, which earlier versions did not read, changes nothing, and neither do transactions in other commodities, or that net to zero.

2024-03-05 * "breakfast"
Assets:Cash -10 CNY
Expenses:Food
time: "08:00:00"
2024-03-05 balance Assets:Cash 100 CNY
time: "09:30:00"

Fix: the notice goes once the time is gone. To assert the balance after the transactions of the day, date the balance on the next day and remove its time, as the web UI writes it. To assert it before them, remove the time.

Beancount resolves this path relative to <file>; write it as <path>

A notice, not an error of the ledger, like BalanceTimeIgnored: the document is listed and opens as before. It is reported on a document of a Beancount ledger whose path names no file relative to the file the document is in, which is where Beancount looks, but names one relative to the ledger root. Earlier versions of Zhang wrote the documents you uploaded so, into files like data/2026/10.bean, which Beancount reports as “File does not exist”. Zhang keeps using the file it finds relative to the ledger root, and the notice gives the path to write instead, in its file and written_as meta. It is given for a ledger on the local disk only; see DocumentNotFound for a remote data source.

data/2026/10.bean
2026-10-04 document Assets:Bank "attachments/3f2a/statement.pdf"

Fix: write the path the notice gives, relative to the file: here "../../attachments/3f2a/statement.pdf". The notice goes, and Beancount finds the file too. Documents uploaded now are written so.

Document file <path> does not exist

A document of a Beancount ledger names a file that does not exist, neither relative to the file the document is in, where Beancount looks, nor relative to the ledger root. Beancount reports it as “File does not exist”. Zhang looks for the files when it loads a ledger on the local disk only, where that costs little: on a remote data source, such as S3, WebDAV or GitHub, neither this error nor DocumentPathRelativeToRoot is reported. There a document is looked for when you open it, relative to its file first, then relative to the ledger root, and opening one found at neither answers that it does not exist.

data/2026/10.bean
; there is no data/2026/statement.pdf
2026-10-04 document Assets:Bank "statement.pdf"

Fix: put the file where the path names it, or correct the path, relative to the file the document is in.

No file in the ledger’s directory matches the include <path>

An include names no file: there is no file at its path, or no file matches its pattern. Zhang loads the rest of the ledger without it, and the path meta holds the path as the include writes it. Beancount reports it too, as File glob "<path>" does not match any files. Zhang reads only within the ledger root: the ledger’s folder on the local disk, or the root of an S3, WebDAV or GitHub data source. A path outside it, an absolute path elsewhere or a relative one climbing out with .., names no file Zhang can read, and is reported the same way.

main.zhang
; there is no accounts/2024.zhang
include "accounts/2024.zhang"

Fix: correct the path, create the file, or remove the include. With a ledger on the local disk, zhang serve reloads the ledger when the missing file is created.

Account does not exist

A directive uses an account that has no open before it: a posting of a transaction, a balance (its account or its pad account), a note, a document, or a close. The account was never opened, opened only later, or its name has a typo.

2024-01-01 open Expenses:Food
2024-01-02 * "Cafe" "lunch"
Assets:Csh -10 CNY
Expenses:Food 10 CNY

Each account is reported once per directive, with the account in the error’s account_name meta. The directive still takes effect: the transaction is booked, the assertion checked. Fix: correct the account name, or add an open dated on or before the first use.

Try to operate a closed account

A directive books on an account after its close took effect: a posting, a pad or a balance … with pad dated after the day of a close with only a date, or later than the time of a close with a time in a zhang file. A second close reports it too. As in Beancount, an account stays usable through the whole day of a close with only a date, and a plain balance, a document and a note, which only record, may follow the close without an error. See When an account is active.

2024-01-01 open Assets:Old-Card
2024-01-01 open Expenses:Food
2024-03-31 close Assets:Old-Card
2024-04-02 * "Cafe" "lunch"
Assets:Old-Card -10 CNY
Expenses:Food 10 CNY

Each account is reported once per transaction, and the transaction is still booked. Fix: post to another account, correct the date, or reopen the account with a new open.

Try to use a undefined commodity

A commodity is used before a commodity directive defines it:

  • a transaction balances in it, as units, price or cost (the error also has the transaction’s txn_id meta);
  • an open lists it, or a price names it;
  • a padding transaction of balance … with pad is in it.

The error has a commodity_name meta naming the commodity. For a transaction with several undefined commodities, it is the first of them in alphabetical order.

2024-01-01 open Assets:Cash
2024-01-01 open Expenses:Food
2024-01-02 * "Cafe" "lunch"
Assets:Cash -10 USD
Expenses:Food 10 USD

The operating currency is defined by its option, so a ledger without that option can use CNY without a commodity directive. Fix: add a commodity directive dated on or before the first use. On the same date, write it above an open that lists it.

Commodity <commodity> is not allowed in <account>: its open lists other commodities

An account was opened with a list of commodities, and something puts another commodity in it. As in Beancount, such an open restricts the account to the commodities it lists. The error is reported on:

  • a posting whose units are in another commodity. Only the units count: the cost and the price of a posting are not checked, and an account that lists AAPL may buy AAPL {90 EUR}. The posting of a transaction that leaves out its amount is checked in the commodity Zhang infers for it;
  • a balance assertion of another commodity, even one that asserts zero;
  • the padding a pad books in another commodity, on the pad, once for each of its two accounts that does not list it. A balance … with pad is one directive: it is reported once for its account, for the assertion and its padding alike, and once for the account it pads from if that one does not list the commodity.

The error has the account_name and commodity metas, and is reported once for each offending posting as written. An open without commodities allows any commodity, and the restriction is for the account itself: its sub-accounts are not restricted. If an account is opened again, the commodities of the latest open before the directive count.

2024-01-01 commodity USD
2024-01-01 commodity EUR
2024-01-01 open Assets:Bank USD
2024-01-01 open Equity:Opening
2024-01-10 * "Deposit in the wrong currency"
Assets:Bank 100 EUR
Equity:Opening -100 EUR

The ledger still loads, and the transaction is still booked as written. Fix: post in one of the commodities the open lists, or add the commodity to the open: 2024-01-01 open Assets:Bank USD, EUR. Leave the list out to allow any commodity.

Not enough commodity lots to book this posting

A posting with a cost reduces more units than the matching lots of the account hold.

2024-01-01 commodity USD
2024-01-01 commodity AAPL
2024-01-01 open Assets:Broker
2024-01-01 open Assets:Cash
2024-01-02 * "Buy"
Assets:Broker 5 AAPL {100 USD}
Assets:Cash -500 USD
2024-02-01 * "Sell"
Assets:Broker -10 AAPL {100 USD}
Assets:Cash 1000 USD

With an explicit cost as above, the transaction is still booked and the lot goes negative. With {}, the entire transaction is rejected, also reports TransactionCannotInferTradeAmount, and keeps the previous holdings. Fix: check the number of units and the cost the posting names; a missing purchase is a common cause. See Lots and cost basis.

Trying to close an account with non zero balance

A close directive closes an account whose own balance, without its sub-accounts, is not zero in some commodity.

2024-01-01 open Assets:Old-Card
2024-01-01 open Equity:Opening-Balances
2024-01-02 * "Opening balance"
Assets:Old-Card 100 CNY
Equity:Opening-Balances
2024-03-31 close Assets:Old-Card

The account is closed anyway. Fix: move the remaining amount to another account before the close.

Budget does not exist

A directive names a budget that is not defined at its date:

  • a budget-add, budget-transfer or budget-close. The directive is ignored.
  • a posting to an account linked to the budget with its budget metadata. It is reported once per account and budget, and the error also has the account_name meta. The posting is not counted toward the budget, and the transaction is still booked.

The error has a budget_name meta naming the budget.

2024-01-01 budget-add Travel 500 CNY

Fix: add the budget directive, dated on or before the first use, or correct the budget’s name.

Trying to define duplicated budget name

A budget directive names a budget that already exists. The second definition is ignored.

2024-01-01 budget Food CNY
2024-02-01 budget Food CNY

Fix: remove the second budget directive, or give it another name.

No price converts {{meta.commodity}} to {{meta.budget_commodity}}, the commodity of budget {{meta.budget_name}}: the amount does not count toward it

An amount of a budget is in another commodity than the budget’s, and no price on or before its date converts it:

  • the amount of a budget-add or budget-transfer;
  • a posting to an account linked to the budget with its budget metadata. The error has the account_name meta.

Zhang converts such an amount to the budget’s commodity at its date, with the prices of the ledger, as convert(position, currency, date) does in a query. Without a price, the amount is left out of the budget: it is never added as a number of another commodity. The error has the budget_name, commodity and budget_commodity metas, and the transaction is still booked.

2024-01-01 budget Food CNY
2024-01-02 budget-add Food 50 USD

Fix: add a price of the commodity in the budget’s commodity, dated on or before the amount, such as 2024-01-01 price USD 7.10 CNY, or write the amount in the budget’s commodity.

Budget {{meta.budget_name}} is closed: postings to {{meta.account_name}} after its close do not count toward it

A posting to an account linked to a budget comes after the budget’s budget-close. A closed budget takes no activity: a budget-close with only a date leaves the budget open through that whole day, and one with a time closes it at that time.

2024-01-01 open Expenses:Food
budget: Food
2024-01-01 budget Food CNY
2024-03-01 budget-close Food
2024-04-05 * "Cafe" "lunch"
Expenses:Food 30 CNY
Assets:Cash

The posting does not count toward the budget, and the transaction is still booked. It is reported once per account and budget, on the first such transaction, with the account_name and budget_name metas. Fix: remove the budget metadata from the account’s open, or close and reopen the account without it, if the account should no longer belong to the budget; or move the budget-close later.

Ledger contains multiple operating currency options, which is not recommended in zhang

The operating_currency option is set more than once. Zhang supports a single operating currency; the value read last is used. Beancount ledgers often set several for Fava.

option "operating_currency" "USD"
option "operating_currency" "EUR"

Fix: keep one operating_currency option.

Directive has an invalid meta value

A metadata value or option that Zhang reads has a value it does not understand:

  • the booking_method metadata of an open, or the default_booking_method option, is not a booking method. The account books with the ledger’s default booking method, and an invalid option leaves the default at FIFO;
  • the timeout, allowed_paths or stage metadata of a plugin directive is invalid. The plugin runs with the default for that capability.
2024-01-01 open Assets:Cash
booking_method: "NON_EXIST"

The ledger still loads. Fix: use a valid value: STRICT, FIFO or LIFO for a booking method; see the plugin capabilities for timeout and allowed_paths.

Booking method is not supported yet, the account uses the default booking method

An account’s booking_method, or the default_booking_method option, is a booking method Zhang does not implement yet: NONE, AVERAGE or AVERAGE_ONLY. The error is reported once, on the open or option directive. The account books with the ledger’s default booking method, and an unsupported option leaves the default at FIFO.

2024-01-01 open Assets:Broker
booking_method: "AVERAGE"

Fix: use one of the supported booking methods: STRICT, FIFO or LIFO.

Reduction matches several lots, which is ambiguous under the STRICT booking method

On an account using the STRICT booking method, a reduction matches several lots and does not reduce all of them in full, so the lot to reduce is ambiguous. This follows Beancount’s STRICT method. The transaction is still booked, like FIFO among the matching lots, so the ledger keeps its numbers until the ambiguity is resolved. The error’s matched_lots meta lists the lots it matched.

2024-01-01 commodity USD
2024-01-01 commodity AAPL
2024-01-01 open Assets:Broker
booking_method: "STRICT"
2024-01-01 open Assets:Cash
2024-01-02 * "Buy"
Assets:Broker 10 AAPL {100 USD}
Assets:Cash -1000 USD
2024-02-01 * "Buy"
Assets:Broker 10 AAPL {110 USD}
Assets:Cash -1100 USD
2024-03-01 * "Sell"
Assets:Broker -5 AAPL {}
Assets:Cash 500 USD

Fix: name the lot to reduce with its cost and acquisition date, such as -5 AAPL {100 USD, 2024-01-02}, or with its label, reduce all matching lots at once, or use the FIFO or LIFO booking method on the account.

Cost merging is not supported yet, the posting books as if its cost had no *

A posting’s cost carries *, Beancount’s merge-cost marker, as in -5 AAPL {*}. In Beancount it asks to merge the account’s lots at their average cost before the reduction, which Beancount reports as not supported yet. Zhang reports the posting once and books it as if the * were not there: {*} books like {}, by the account’s booking method, and {*, "lot-a"} like {"lot-a"}. The file is written back with the *.

2024-01-01 commodity USD
2024-01-01 commodity AAPL
2024-01-01 open Assets:Broker
2024-01-01 open Assets:Cash
2024-01-01 open Income:Gains
2024-01-02 * "buy"
Assets:Broker 10 AAPL {185 USD}
Assets:Cash -1850 USD
2024-02-01 * "sell"
Assets:Broker -5 AAPL {*}
Assets:Cash 1000 USD
Income:Gains

The ledger still loads, and the transaction is booked. Fix: write the cost of the lots to reduce, or {} to let the booking method choose. An average cost needs the AVERAGE booking method, which is not implemented either; see UnsupportedBookingMethod.

Plugin <plugin>: <message>

A WASM plugin declared with a plugin directive reported a problem, usually a validator that checks the ledger without changing it. The plugin reports it through the zhang_emit_error host function, and the ledger still loads. The error’s message meta describes the problem and its plugin meta names the plugin; the plugin may add metas of its own. The error points at the directive the plugin names, or at the plugin’s plugin directive when it names none. Writing Plugins shows how a plugin reports one.

For example, a plugin that requires a payee on every transaction reports message: "payee is missing" on this transaction:

2024-01-01 open Assets:Cash
2024-01-01 open Expenses:Food
2024-01-02 * "lunch"
Assets:Cash -10 CNY
Expenses:Food 10 CNY

Fix: do what the message meta asks, or change the plugin’s settings. A message saying that the plugin called zhang_emit_error with an invalid payload is a bug in the plugin: report it to the plugin’s author.

These problems stop Zhang from loading the ledger. zhang serve prints the reason on stderr and exits with exit code 1. When the server is already running, a reload that fails keeps the ledger as it was, logs the reason, and shows it in the web UI until a reload succeeds.

  • A syntax error in a file. The message names the file, line and column, such as failed to parse zhang file: unexpected input at line 4, column 3.
  • A main file whose extension tells no format, such as main.txt or main. The message names the main file and the extensions that select a format: .zhang, or .bean, .beancount and .bc for beancount.
  • A file that is not UTF-8 text, such as one saved as Latin-1 or GBK. The message names the file and the line of the first byte that is not UTF-8, such as the file data/2024.zhang is not UTF-8 text: line 3 holds a byte that is not UTF-8. Save the file with the UTF-8 encoding. A file that starts with a byte order mark is UTF-8 text.
  • An invalid value for the default_rounding or directive_output_path option, or for the rounding metadata of a commodity. The message is option value is invalid.
  • A plugin whose module is missing or cannot be loaded, or whose call fails or runs past its timeout, while plugins are enabled. See Plugin.