Skip to content

chobo diagnostics

Every code chobo prints, when it appears, and how to fix it. For one of them: chobo explain E020.

Code Severity Title
E001 error A line that does not read
E002 error A name that is not declared
E003 error Declared twice
E004 error The wrong number of arguments for an account
E005 error A parameter used as the wrong type
E010 error A move between units that differ
E011 error A number that does not fit its unit
E012 error A move from an account to the same account
E013 error A transfer with no move
E014 error A tax on a unit that is not money
E020 error An account with no bound
E021 error An outside account with a bound
E022 error Bounds that leave no balance
E023 error A bound with no reason to refuse with
E030 error A transfer with no key
E031 error An amount in the key
E040 error A hold that does not say how it ends
E041 error An expiry out of range
E050 error An account kind changed since the revision compared with
E051 error A transfer kind changed since the revision compared with
E060 error An operation that does not fit one TigerBeetle request
E061 error A name too long for PostgreSQL
E062 error A name MySQL cannot hold as written
E063 error A name Rust takes as no identifier
W101 warning An account that only fills
W102 warning A transfer that is always refused
W103 warning A transfer refused for the order of its moves
W104 warning A move of a hold that counts on another move of the same hold
W105 warning Declared and never used
W106 warning A bound that never matters
W107 warning An account or transfer kind gone since the revision compared with
W901 warning A key is written in the file

E001

error — A line that does not read

When. A word chobo does not know, an indentation that does not line up, a string left open, or a line that is not in one of the forms. A byte order mark, a tab and a combining mark (a voiced sound mark or an accent written as a character of its own) stop here too. With a line it cannot read, anything after would be a guess, so the names are not resolved and nothing else is checked.

Fix. Rewrite the line in the form the message gives. Under an account go description, at least and at most; under a transfer go description, key, pending and move. Write a letter with a combining mark as the one precomposed character.

Smallest reproduction:

book shop v1
unit pcs
account stock(sku: string) : pcs
  at least 0 refused as out_of_stock
  keep 3

Related codes: E002

E002

error — A name that is not declared

When. A unit, an account, a type or a parameter of the transfer is named that nothing declares. Names are compared exactly as written.

Fix. Declare it, or correct the name to one that is declared. An account for the world outside the book (a supplier, the customers) is declared too: account supplier : pcs outside.

Smallest reproduction:

book shop v1
unit pcs
account stock(sku: string) : pcs
  at least 0 refused as out_of_stock
transfer receive(note: string, sku: string, qty: pcs)
  key note, sku
  move qty from supplier to stock(sku)

Related codes: E001, E004

E003

error — Declared twice

When. A unit, an account, a transfer or a parameter is declared twice under one name; or description, at least, at most, key or pending appears twice under one account or transfer; or the key lists a parameter twice.

Fix. Remove one, or give it another name. The key goes on one line.

Smallest reproduction:

book shop v1
unit pcs
account stock(sku: string) : pcs
  at least 0 refused as out_of_stock
account stock(sku: string) : pcs
  at least 0 refused as out_of_stock

E004

error — The wrong number of arguments for an account

When. A move gives an account more or fewer arguments than the account declares. Arguments are passed by position, in the order they are declared.

Fix. Give as many as the account declares: stock(sku) for account stock(sku: string). An account without parameters is written with no parentheses at all: customers.

Smallest reproduction:

book shop v1
unit pcs
account stock(sku: string) : pcs
  at least 0 refused as out_of_stock
account supplier : pcs outside
transfer receive(note: string, sku: string, qty: pcs)
  key note, sku
  move qty from supplier to stock

Related codes: E002, E005

E005

error — A parameter used as the wrong type

When. A string parameter is the amount of a move, an amount parameter is an account's argument, or an account declares a parameter of a type other than string.

Fix. An amount is a parameter whose type is a unit (qty: pcs), or a number; an account's argument is a string parameter, or a string.

Smallest reproduction:

book shop v1
unit pcs
account stock(sku: string) : pcs
  at least 0 refused as out_of_stock
account supplier : pcs outside
transfer receive(note: string, sku: string, qty: pcs)
  key note, sku
  move sku from supplier to stock(sku)

Related codes: E010

E010

error — A move between units that differ

When. The account a move takes from, the account it puts into and its amount are not all in one unit. chobo does not convert.

Fix. Make them one unit. To exchange one unit for another, write two moves, each through an outside account of its own unit, with amounts the caller works out.

Smallest reproduction:

book shop v1
unit pcs
unit yen
account stock(sku: string) : pcs
  at least 0 refused as out_of_stock
account customers : yen outside
transfer ship(order: string, sku: string, qty: pcs)
  key order, sku
  move qty from stock(sku) to customers

Related codes: E005

E011

error — A number that does not fit its unit

When. A number written in the book (a bound, or the amount of a move) has more decimal places than the unit's scale, an amount is negative, or a number is outside −(2⁶³ − 1) to 2⁶³ − 1, counted in the unit's smallest step.

Fix. Write it with no more decimal places than the unit's scale, and declare a unit that needs them as unit USD scale 2. To move the other way, swap from and to.

Smallest reproduction:

book shop v1
unit pcs
account stock(sku: string) : pcs
  at least 0.5 refused as out_of_stock

Related codes: E022

E012

error — A move from an account to the same account

When. One move names the same account with the same arguments in from and in to. Every call is refused with same_account.

Fix. Move to another account; to move between two accounts of one kind, give them different arguments (stock(from_sku) to stock(to_sku)).

Smallest reproduction:

book shop v1
unit pcs
account stock(sku: string) : pcs
  at least 0 refused as out_of_stock
transfer shuffle(note: string, sku: string, qty: pcs)
  key note
  move qty from stock(sku) to stock(sku)

E013

error — A transfer with no move

When. A transfer has no move line.

Fix. Write where it moves from and to: move <amount> from <account> to <account>.

Smallest reproduction:

book shop v1
unit pcs
account stock(sku: string) : pcs
  at least 0 refused as out_of_stock
transfer count(note: string)
  key note

E014

error — A tax on a unit that is not money

When. A unit line ends with incl_tax or excl_tax, and the unit is not money. A unit of money is named for a currency (円, JPY, an ISO 4217 code): yen and JPY with scale 0, any other currency with scale 0 or 2. Any other unit (pcs, kg, yen with scale 2) is neither with tax nor without.

Fix. Take incl_tax or excl_tax off, or name the unit for a currency (unit JPY incl_tax, unit USD scale 2 excl_tax). Whether it is with tax or without is written so that an amount handed over from a rule or a workflow is taken only when it says the same.

Smallest reproduction:

book shop v1
unit pcs incl_tax
account stock(sku: string) : pcs
  at least 0 refused as out_of_stock

Related codes: E010

E020

error — An account with no bound

When. An account not marked outside has neither at least nor at most. The bounds of accounts are all chobo keeps, so such an account would keep nothing.

Fix. Give it a bound, like at least 0 refused as out_of_stock; or mark it outside if it stands for the world outside the book (a supplier, the customers, a bank).

Smallest reproduction:

book shop v1
unit pcs
account stock(sku: string) : pcs

Related codes: E021

E021

error — An outside account with a bound

When. An account marked outside has at least or at most. An outside account stands for the world outside the book, and may go below 0.

Fix. Remove the bound; or, to keep that balance, remove outside so that the book keeps the account.

Smallest reproduction:

book shop v1
unit pcs
account supplier : pcs outside
  at least 0 refused as supplier_short

Related codes: E020

E022

error — Bounds that leave no balance

When. The lower bound is above the upper bound, or the upper bound is below 0. An account starts at 0, so below 0 nothing could ever be put into it.

Fix. Make the lower bound no higher than the upper bound, and the upper bound 0 or more.

Smallest reproduction:

book shop v1
unit pcs
account shelf(sku: string) : pcs
  at least 10 refused as below_safety_stock
  at most 5 refused as shelf_full

Related codes: E011

E023

error — A bound with no reason to refuse with

When. A bound has no refused as <reason>, or its reason is one of the names chobo gives itself (key_conflict, already_refused, same_account, no_such_hold, already_posted, already_voided, expired, over_hold). The reason is what a caller gets back when the bound refuses, and it is the name of the error of a dandori task.

Fix. Name it in the business's words: at least 0 refused as out_of_stock.

Smallest reproduction:

book shop v1
unit pcs
account stock(sku: string) : pcs
  at least 0

E030

error — A transfer with no key

When. A transfer has no key line, or the key names something that is not one of its parameters. Every transfer needs an idempotency key: a second call with the same key and the same content does nothing, and one with other content is refused.

Fix. List the parameters it is to happen once for: once per order and SKU is key order, sku.

Smallest reproduction:

book shop v1
unit pcs
account stock(sku: string) : pcs
  at least 0 refused as out_of_stock
account supplier : pcs outside
transfer receive(note: string, sku: string, qty: pcs)
  move qty from supplier to stock(sku)

Related codes: E031

E031

error — An amount in the key

When. The key lists a parameter whose type is a unit. A retry with another amount would then go through as a second transfer, and move twice.

Fix. Take the amount out of the key; a second call that differs only in the amount is then refused with key_conflict.

Smallest reproduction:

book shop v1
unit pcs
account stock(sku: string) : pcs
  at least 0 refused as out_of_stock
account supplier : pcs outside
transfer receive(note: string, sku: string, qty: pcs)
  key note, qty
  move qty from supplier to stock(sku)

Related codes: E030

E040

error — A hold that does not say how it ends

When. pending stands alone, with neither an expiry nor never expires. A hold ends when it is posted, voided or expires.

Fix. Write pending expires after 30 minutes or pending never expires. Only the caller ends a hold that never expires.

Smallest reproduction:

book shop v1
unit pcs
account stock(sku: string) : pcs
  at least 0 refused as out_of_stock
account customers : pcs outside
transfer reserve(order: string, sku: string, qty: pcs)
  key order, sku
  pending
  move qty from stock(sku) to customers

Related codes: E041

E041

error — An expiry out of range

When. The expiry is under 1 second, over 4294967295 seconds (2³² − 1 seconds, about 136 years), or not a whole number. TigerBeetle's timeout is 32 bits of seconds.

Fix. Write a whole number from 1 second to 2³² − 1 seconds; to hold for longer than that, use never expires.

Smallest reproduction:

book shop v1
unit pcs
account stock(sku: string) : pcs
  at least 0 refused as out_of_stock
account customers : pcs outside
transfer reserve(order: string, sku: string, qty: pcs)
  key order, sku
  pending expires after 0 minutes
  move qty from stock(sku) to customers

Related codes: E040

E050

error — An account kind changed since the revision compared with

When. An account kind of the revision given to --diff-base has a new unit, scale, parameters or bound, or is outside where it was not (or the other way round). The book's version is not part of an account's identity, so the accounts that exist keep the old definition.

Fix. Declare an account under a new name, and write a transfer that moves the balances over. Make a limit that may change an account from the start (a member's credit limit becomes an account of credit left).

The book at the revision compared with:

book shop v1
unit pcs
account stock(sku: string) : pcs
  at least 0 refused as out_of_stock
account supplier : pcs outside
account customers : pcs outside
transfer receive(note: string, sku: string, qty: pcs)
  key note, sku
  move qty from supplier to stock(sku)
transfer ship(order: string, sku: string, qty: pcs)
  key order, sku
  move qty from stock(sku) to customers

The book now (the smallest reproduction):

book shop v1
unit pcs
account stock(sku: string) : pcs
  at least 3 refused as out_of_stock
account supplier : pcs outside
account customers : pcs outside
transfer receive(note: string, sku: string, qty: pcs)
  key note, sku
  move qty from supplier to stock(sku)
transfer ship(order: string, sku: string, qty: pcs)
  key order, sku
  move qty from stock(sku) to customers

Related codes: E051, W107

E051

error — A transfer kind changed since the revision compared with

When. A transfer kind of the revision given to --diff-base has new parameters, a new key, a new way for its holds to end, or new moves. A retry in flight would be refused with key_conflict, and a hold still held could not be posted.

Fix. Declare the new form under a new name and keep the old one; remove the old one once every hold it made has ended.

The book at the revision compared with:

book shop v1
unit pcs
account stock(sku: string) : pcs
  at least 0 refused as out_of_stock
account supplier : pcs outside
account customers : pcs outside
transfer receive(note: string, sku: string, qty: pcs)
  key note, sku
  move qty from supplier to stock(sku)
transfer ship(order: string, sku: string, qty: pcs)
  key order, sku
  move qty from stock(sku) to customers

The book now (the smallest reproduction):

book shop v1
unit pcs
account stock(sku: string) : pcs
  at least 0 refused as out_of_stock
account supplier : pcs outside
account customers : pcs outside
transfer receive(note: string, sku: string, qty: pcs)
  key note, sku
  move qty from supplier to stock(sku)
transfer ship(order: string, sku: string, qty: pcs)
  key order
  move qty from stock(sku) to customers

Related codes: E050, W107

E060

error — An operation that does not fit one TigerBeetle request

When. With a TigerBeetle target of chobo build: one operation of a transfer kind sends a chain of more than 253 transfers. The chain has to go in one request to go through whole or not at all, and a replica started with --development takes at most 253 in one (8189 without it). A move between bounded accounts takes up to 6 transfers.

Fix. Split the transfer kind into kinds with fewer moves. Each kind is a write of its own, so keep together the moves that have to go through all or none.

The command that shows it: chobo build <file.book> --target tigerbeetle-typescript

Smallest reproduction:

book split v1
unit yen
account pool(id: string) : yen
  at least 1 refused as pool_short
  at most 1000000 refused as pool_full
account credit(id: string) : yen
  at least -1000 refused as over_limit
  at most 1000 refused as overpaid
account bank : yen outside
transfer fund(note: string, id: string, amount: yen)
  key note
  move amount from bank to pool(id)
transfer repay(note: string, id: string, amount: yen)
  key note
  move amount from credit(id) to bank
transfer spread(note: string, a: string, b: string, amount: yen)
  key note
  move amount from pool(a) to credit(b)
  move amount from pool(a) to credit(b)
  move amount from pool(a) to credit(b)
  move amount from pool(a) to credit(b)
  move amount from pool(a) to credit(b)
  move amount from pool(a) to credit(b)
  move amount from pool(a) to credit(b)
  move amount from pool(a) to credit(b)
  move amount from pool(a) to credit(b)
  move amount from pool(a) to credit(b)
  move amount from pool(a) to credit(b)
  move amount from pool(a) to credit(b)
  move amount from pool(a) to credit(b)
  move amount from pool(a) to credit(b)
  move amount from pool(a) to credit(b)
  move amount from pool(a) to credit(b)
  move amount from pool(a) to credit(b)
  move amount from pool(a) to credit(b)
  move amount from pool(a) to credit(b)
  move amount from pool(a) to credit(b)
  move amount from pool(a) to credit(b)
  move amount from pool(a) to credit(b)
  move amount from pool(a) to credit(b)
  move amount from pool(a) to credit(b)
  move amount from pool(a) to credit(b)
  move amount from pool(a) to credit(b)
  move amount from pool(a) to credit(b)
  move amount from pool(a) to credit(b)
  move amount from pool(a) to credit(b)
  move amount from pool(a) to credit(b)
  move amount from pool(a) to credit(b)
  move amount from pool(a) to credit(b)
  move amount from pool(a) to credit(b)
  move amount from pool(a) to credit(b)
  move amount from pool(a) to credit(b)
  move amount from pool(a) to credit(b)
  move amount from pool(a) to credit(b)
  move amount from pool(a) to credit(b)
  move amount from pool(a) to credit(b)
  move amount from pool(a) to credit(b)
  move amount from pool(a) to credit(b)
  move amount from pool(a) to credit(b)
  move amount from pool(a) to credit(b)

Related codes: E061, E062, E063

E061

error — A name too long for PostgreSQL

When. With a PostgreSQL target of chobo build: the name of the schema (the book's), of a function (<transfer>_<operation>, balance_<account>) or of a function's parameter (p_<parameter>) is longer than 63 bytes. PostgreSQL cuts a longer name short without a word, so two functions could end up with one name. A Japanese character is 3 bytes.

Fix. Make the name shorter: 63 ASCII letters, or 21 Japanese characters, less what follows it in a function's name (_hold, …).

The command that shows it: chobo build <file.book> --target postgres

Smallest reproduction:

book shop v1
unit pcs
account stock(sku: string) : pcs
  at least 0 refused as out_of_stock
account supplier : pcs outside
account customers : pcs outside
transfer receive(note: string, sku: string, qty: pcs)
  key note, sku
  move qty from supplier to stock(sku)
transfer ship_the_goods_to_the_customers_who_have_ordered_and_paid_for_them(order: string, sku: string, qty: pcs)
  key order, sku
  move qty from stock(sku) to customers

Related codes: E060, E062, E063

E062

error — A name MySQL cannot hold as written

When. With a target of the MySQL family of chobo build: a name made from the book's, of a table (<book>_accounts, …) or of a check (<book>_within_lower, …), is longer than 64 characters, or the book's name has a character beyond the Basic Multilingual Plane (𠮷 and the like). MySQL writes such a character as ? with only a warning, so two names could become one. TiDB and Vitess keep the same rules.

Fix. Make the book's name at most 51 characters (_within_lower follows it), and take out any character beyond the Basic Multilingual Plane.

The command that shows it: chobo build <file.book> --target mysql

Smallest reproduction:

book stock_of_the_central_warehouse_and_of_each_branch_stores v1
unit pcs
account stock(sku: string) : pcs
  at least 0 refused as out_of_stock
account supplier : pcs outside
account customers : pcs outside
transfer receive(note: string, sku: string, qty: pcs)
  key note, sku
  move qty from supplier to stock(sku)
transfer ship(order: string, sku: string, qty: pcs)
  key order, sku
  move qty from stock(sku) to customers

Related codes: E060, E061, E063

E063

error — A name Rust takes as no identifier

When. With a Rust target of chobo build (postgres-rust, tigerbeetle-rust, mysql-rust): the name of a transfer, an account or a parameter, or a reason, has a character no Rust identifier holds where the name has it. The Rust client writes those names as they are, as methods, fields of structs, and names of types and of an enum's variants. A name of chobo may hold any letter and digit of Unicode; a Rust identifier begins with a character of XID_Start and goes on with those of XID_Continue (Unicode 17.0, which rustc 1.94 to 1.97 read), so it holds no digit that is not decimal (², ½, ①, …) and no circled letter (Ⓐ), and begins with no combining mark.

Fix. Take the character out of the name, or write another in its place (area_m² as area_m2, for one). The same name is in the clients of every language, so a call in any of them is then written with the new name.

The command that shows it: chobo build <file.book> --target postgres-rust

Smallest reproduction:

book rooms v1
unit sqm
account floor(site: string) : sqm
  at least 0 refused as no_room
account builders : sqm outside
account tenants : sqm outside
transfer build(permit: string, site: string, area_m²: sqm)
  key permit, site
  move area_m² from builders to floor(site)

Related codes: E061, E062

W101

warning — An account that only fills

When. Moves put into an account the book keeps, and no move anywhere takes out of it: whatever goes in stays.

Fix. Write a transfer that takes out of it, or mark it outside if it stands for the world outside the book.

Smallest reproduction:

book shop v1
unit yen
account wallet(member: string) : yen
  at least 0 refused as short
account bank : yen outside
transfer top_up(slip: string, member: string, amount: yen)
  key slip
  move amount from bank to wallet(member)

Related codes: W102, W106

W102

warning — A transfer that is always refused

When. What a transfer takes from has a lower bound of 0 or more, and nothing anywhere puts into it: whenever the transfer moves more than 0, it is refused.

Fix. Write a transfer that puts into that account (a delivery, a top-up).

Smallest reproduction:

book shop v1
unit pcs
account stock(sku: string) : pcs
  at least 0 refused as out_of_stock
account customers : pcs outside
transfer ship(order: string, sku: string, qty: pcs)
  key order, sku
  move qty from stock(sku) to customers

Related codes: W101

W103

warning — A transfer refused for the order of its moves

When. In a transfer that posts at once, an earlier move takes from an account that a later move puts into (a lower bound), or puts into one that a later move takes from (an upper bound). Moves are checked one at a time in the order they are written, so the earlier move is refused even when the two together would fit.

Fix. Write first the move the other one counts on: for a lower bound the one that puts in, for an upper bound the one that takes out.

Smallest reproduction:

book market v1
unit yen
account buyers : yen outside
account fees : yen outside
account sales(shop: string) : yen
  at least 0 refused as sales_short
transfer sell(order: string, shop: string, price: yen, fee: yen)
  key order
  move fee from sales(shop) to fees
  move price from buyers to sales(shop)

Related codes: W104

W104

warning — A move of a hold that counts on another move of the same hold

When. In a hold, one move takes from an account another move of the same hold puts into (or puts into one another move takes from). What is held cannot be spent where it comes in, nor makes room where it goes out, so changing the order does not help.

Fix. Have the amount in that account beforehand, or move it in another transfer after the hold is posted.

Smallest reproduction:

book market v1
unit yen
account buyers : yen outside
account payouts : yen outside
account escrow(order: string) : yen
  at least 0 refused as escrow_short
transfer pay(order: string, price: yen)
  key order
  pending expires after 1 hour
  move price from buyers to escrow(order)
  move price from escrow(order) to payouts

Related codes: W103

W105

warning — Declared and never used

When. A unit that no account or parameter uses, an account no move names, or a parameter that is in neither the key nor any move.

Fix. Remove it, or write where it is used.

Smallest reproduction:

book shop v1
unit pcs
unit boxes
account stock(sku: string) : pcs
  at least 0 refused as out_of_stock
account supplier : pcs outside
account customers : pcs outside
transfer receive(note: string, sku: string, qty: pcs)
  key note, sku
  move qty from supplier to stock(sku)
transfer ship(order: string, sku: string, qty: pcs)
  key order, sku
  move qty from stock(sku) to customers

Related codes: W106

W106

warning — A bound that never matters

When. Nothing puts into an account with an upper bound (or takes out of one with a lower bound), so the bound never refuses anything.

Fix. Remove the bound, or write the transfer it was meant to check.

Smallest reproduction:

book shop v1
unit yen
account credit(member: string) : yen
  at least -1000 refused as over_limit
  at most 0 refused as overpaid
account shops : yen outside
transfer spend(slip: string, member: string, amount: yen)
  key slip
  move amount from credit(member) to shops

Related codes: W101, W105

W107

warning — An account or transfer kind gone since the revision compared with

When. An account or transfer kind of the revision given to --diff-base is gone, or the book has a new name. Its balances and holds stay in the database.

Fix. Move the balances out with a transfer before removing it, or keep it.

The book at the revision compared with:

book shop v1
unit pcs
account stock(sku: string) : pcs
  at least 0 refused as out_of_stock
account supplier : pcs outside
account customers : pcs outside
transfer receive(note: string, sku: string, qty: pcs)
  key note, sku
  move qty from supplier to stock(sku)
transfer ship(order: string, sku: string, qty: pcs)
  key order, sku
  move qty from stock(sku) to customers

The book now (the smallest reproduction):

book shop v1
unit pcs
account stock(sku: string) : pcs
  at least 0 refused as out_of_stock
account supplier : pcs outside
account customers : pcs outside
transfer receive(note: string, sku: string, qty: pcs)
  key note, sku
  move qty from supplier to stock(sku)
transfer send(order: string, sku: string, qty: pcs)
  key order, sku
  move qty from stock(sku) to customers

Related codes: E050, E051

W901

warning — A key is written in the file

When. Somewhere in the book, in a string or a comment alike, there is a value in the shape of a key: an AWS access key ID, a key or token of GitHub, Slack, Stripe, OpenAI, Anthropic or Google, a Slack incoming webhook URL, or a PEM private key, each a shape its provider fixes. Every language of ritsu looks for them the same way. The diagnostic gives the kind of key, its prefix and its length, and never the key nor its line.

Fix. Keep the key where the code runs (an environment variable, the platform's connection or secret store) and read it from there. If it is real, revoke it with its provider first: taking it out of the file leaves it in the history of the repository. If it is a value for tests, write ritsu: test secret in a comment on the same line.

Smallest reproduction:

book wallet v1
# the Google Maps API key the shops look addresses up with: AIzaSyD-ritsu-fake-key-for-tests-000000
unit jpy
account balance(customer: string) : jpy
  at least 0 refused as not_enough
account cards : jpy outside
account shops : jpy outside
transfer top_up(payment: string, customer: string, amount: jpy)
  key payment
  move amount from cards to balance(customer)
transfer buy(order: string, customer: string, amount: jpy)
  key order
  move amount from balance(customer) to shops