Repository navigation
Conversation
- A --gateway that cannot be a canister (the anonymous principal, the management canister, an identity's principal) is refused before any network is asked; a canister that serves no get_card_terms (IC0536) is said to be no gateway; and a boundary node's HTTP 4xx canister_not_found reads as the gateway being absent, as a replica's IC0301 does. - With neither --amount nor --cycles, clap names both. A negative value of either (-1, -5t) is refused by clap in the same words and with exit status 2, where clap read -5t as an unknown flag. - -n local outside a project says there is no such network outside one, and points at -n ic; the mainnet hint reads "use -n ic", without backticks. - CyclePay, and Stripe's page, are named only for CyclePay's gateway; another is "the gateway", with "the gateway's payment page". - An error that is the whole story is wrapped to 80 columns, as the shortfall hint's are, rather than left for the terminal to break mid-word; one with a cause keeps its chain.
- The prompt is drawn on stderr and reads stdin, so both must be a terminal: with stderr redirected (2>&1 | tee) the command refuses and names --yes, where dialoguer failed with "IO error: not a terminal". - Ctrl-C and SIGTERM are caught before the question is asked: Ctrl-C there is an answer, which ends the prompt's line, shows the cursor again and creates no order (exit 1), where it ended the process with the cursor hidden. - From the prompt on, the signals stay caught, so every step after it races its call against them: create_order, which a stalled network can hold for minutes (the command now stops at once and says the order may or may not exist, and on CyclePay how to find out: the same amount asked for again within the hour hands back the open order), the wait, which takes the same listeners, and the balance read after a delivery, which is left out. - "Nothing is charged until..." is said on a line of its own, so that the question stays short and a narrow terminal does not draw it twice. The prompt's terminals and answer go through a small Prompt trait, so that the tests pin the stderr check, the catch before the question (a SIGINT raised while it is asked would end the test binary otherwise) and the cursor's return, and a stalled create_order gives way to a ready interrupt.
- The wait judges silence by the clock: it says the gateway is not answering once no poll has been answered for 9 seconds, a poll in flight or not, and gives up once five minutes pass without word of the order, whether the network refuses each poll at once or stalls it. A poll counts as unanswered after 10 seconds. The gateway answering again is said too. - However the wait ends unsettled (Ctrl-C, the hour, the gateway out of reach), the command says once where the order stands: payable until its deadline while unpaid, delivered once paid. Every message is short sentences wrapped at 80 columns, with each command on a line of its own, indented, without backticks: icp cycles balance for the cycles, and icp canister call <gateway> order_status for a refund under way or an order an operator handles, both from commands::args::selection_flags, as the identity (the default named, since only the owner can read an order) and on the network the run used. --no-wait says the same. - A payment that has not settled is "Payment pending", not received; a refund under way is not called final; the gateway's note on a paid order (why its delivery waits) is shown, cut with … to fit the live line and whole off a terminal. - The live line may reach the terminal's last column, which indicatif pads to anyway; its comment said otherwise. - --json prints a last line however the wait ends, with `ended` (settled, interrupted, timed_out, contact_lost, not_found); the first names the gateway and the buyer. - The QR code is black on white, from the 256 colours' fixed black and white (16 and 231), set on every row and reset at its end, with a white quiet zone: four modules across, and four down where the terminal has the rows, else two, with a blank line above where one is spare. With colour off (NO_COLOR, TERM=dumb) it is 1.7.0's drawing, with no escape sequence in it. 1.7.0 drew it in the terminal's colours, inverted on a light theme. - --cycles is "Wanted: <cycles>" over an estimate, not "at least". The stand-in gateway learns a pending payment and a refund under way.
This was referenced Oct 16, 2026
Collaborator
|
closing this - it's replaced by the stack above PR825 |
raymondk
pushed a commit
that referenced
this pull request
Oct 20, 2026
…ays how the wait ended - The first --json line, the order as made, gains `gateway` (the canister id the order was placed with) and `buyer` (the principal that placed it, whose Cycles Ledger account the cycles go to). - The last --json line is printed however the wait ends, not only once the order settles, and says why in `ended`: settled, interrupted, timed_out, contact_lost or not_found. It carries the order as last known (as made, with no status, when the gateway no longer knows it), and gains `expires_at`: until when an unpaid order stays payable. Every key is always present, null where it does not apply. -q still prints the status tag only for a settled order. - The quote's "Wanted:" line says what --cycles asked for without "at least": what is asked for, not a promise, since the gateway's conversion can land a little under the estimate. Extracted from PR #824. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Twxau844tMTwyHpQJEbmBo
raymondk
pushed a commit
that referenced
this pull request
Oct 20, 2026
…ays how the wait ended - The first --json line, the order as made, gains `gateway` (the canister id the order was placed with) and `buyer` (the principal that placed it, whose Cycles Ledger account the cycles go to). - The last --json line is printed however the wait ends, not only once the order settles, and says why in `ended`: settled, interrupted, timed_out, contact_lost or not_found. It carries the order as last known (as made, with no status, when the gateway no longer knows it), and gains `expires_at`: until when an unpaid order stays payable. Every key is always present, null where it does not apply. -q still prints the status tag only for a settled order. - The quote's "Wanted:" line says what --cycles asked for without "at least": what is asked for, not a promise, since the gateway's conversion can land a little under the estimate. Extracted from PR #824. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Twxau844tMTwyHpQJEbmBo
raymondk
added a commit
that referenced
this pull request
Oct 20, 2026
…uses negatives (#826) Before, `icp cycles buy` with neither flag said only that --amount was required, as if --cycles were not a way in; and a negative value of either was read by clap as a flag nobody passed (`-5t` was "unexpected argument '-5t'"), with no word about the sign. Now a clap ArgGroup names both flags as the one required argument, and the two options take a leading `-` as part of their value and refuse it in words ("cannot be negative: give the cycles to receive, such as 5t"), with exit status 2 for both. Extracted from PR #824. Claude-Session: https://claude.ai/code/session_01Twxau844tMTwyHpQJEbmBo Co-authored-by: Josh <49500097+JoshDFN@users.noreply.github.com> Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
raymondk
added a commit
that referenced
this pull request
Oct 20, 2026
…not a gateway (#827) A --gateway that cannot be a canister (the anonymous principal, the management canister, an identity's principal) is refused before any network is asked; a canister that serves no get_card_terms is said to be no gateway, where the user saw the replica's raw IC0536 "has no query method" rejection; and a boundary node's HTTP 4xx canister_not_found reads as the gateway being absent, as a replica's IC0301 does, where the user saw the HTTP routing error. Names, in buy.rs, is how the command's prose names the gateway: CyclePay, and Stripe's page, for CyclePay's own gateway; "the gateway" and "the gateway's payment page" for any other. Extracted from PR #824. Claude-Session: https://claude.ai/code/session_01Twxau844tMTwyHpQJEbmBo Co-authored-by: Josh <49500097+JoshDFN@users.noreply.github.com> Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
raymondk
pushed a commit
that referenced
this pull request
Oct 20, 2026
…ays how the wait ended - The first --json line, the order as made, gains `gateway` (the canister id the order was placed with) and `buyer` (the principal that placed it, whose Cycles Ledger account the cycles go to). - The last --json line is printed however the wait ends, not only once the order settles, and says why in `ended`: settled, interrupted, timed_out, contact_lost or not_found. It carries the order as last known (as made, with no status, when the gateway no longer knows it), and gains `expires_at`: until when an unpaid order stays payable. Every key is always present, null where it does not apply. -q still prints the status tag only for a settled order. - The quote's "Wanted:" line says what --cycles asked for without "at least": what is asked for, not a promise, since the gateway's conversion can land a little under the estimate. Extracted from PR #824. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Twxau844tMTwyHpQJEbmBo
raymondk
pushed a commit
that referenced
this pull request
Oct 20, 2026
…of the order The wait polled the gateway every three seconds with no time limit of its own on a poll, so a stalled connection held it for the HTTP client's six minutes; the give-up rule counted a hundred consecutive failed polls, which took five minutes on a network that refuses and ten hours on one that stalls; and the hour limit was only checked after a successful poll, so it never fired while the gateway was unreachable. Now a poll gets ten seconds, and after five minutes without word of the order the wait gives up with `ContactLost`, carrying the order as last seen over the last poll's failure. Both that and the hour are judged on the clock after every poll, answered or not. Still a query every three seconds; nothing else about the order changes. Code by Josh, cut down from #824 (commit 9cd3e1c, wait part only). Not included: the live line saying the gateway has gone quiet and is answering again, and the events that drove it. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015aem4tPs4TYVZb8k4LMbJJ
raymondk
pushed a commit
that referenced
this pull request
Oct 20, 2026
…how to look in on it
When the wait ended with the order unsettled (Ctrl-C, the hour, the
gateway out of reach) the command said one line ("Order co-1 stays
payable until ...", "Order co-1 is paid, delivering.") and a fixed hint
naming `icp cycles balance` without the flags the run used, then ended
with a short error of its own; --no-wait said the same hint. An order
an operator handles (a refund owed, or one needing review) said only
"Keep the order id".
Now the command says once, as the error it ends with, why the wait
ended (stopped; an hour passed; the gateway stopped answering), whether
the order stays payable until its deadline, is paid and being delivered,
or is in some other state, where its cycles land, and the
`icp cycles balance` command that shows them, on a line of its own with
the identity (the default named, since only the identity that made an
order can read it), network and environment the run used. --no-wait
says the same. A refund owed and an order needing review come with the
`icp canister call <gateway> order_status --query ... '("<id>")'`
command that reads the order back. Paragraphs are set apart by a blank
line, commands are indented and never wrapped, and a value no quoting
carries in every shell is left as <your identity> with a note. -q keeps
the status alone, as before.
The flags come from a builder this commit adds,
commands::args::selection_flags (PasteFlags, shell_word): the short
flag where there is one, `--flag=value` for a value starting with `-`,
which would otherwise read as a flag, and a value bare when nothing in
it is special to any of the POSIX shells, fish, elvish or PowerShell,
else single-quoted, else (for a name with `'`) double-quoted when
nothing else in it is special there; anything else is a placeholder,
`--flag=<placeholder>` when the value starts with `-`. Unit tests pin
every quoted word and every flag form. The builder was the first commit
of #823 and then #829, where it rewrote the recovery command
`icp canister create --with-icp` prints; that path is rare enough to be
left as it is.
The stand-in gateway's tests buy as a named identity, `buyer`, so the
printed flags can be asserted.
Extracted from PR #824.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015aem4tPs4TYVZb8k4LMbJJ
raymondk
pushed a commit
that referenced
this pull request
Oct 20, 2026
…ays how the wait ended - The first --json line, the order as made, gains `gateway` (the canister id the order was placed with) and `buyer` (the principal that placed it, whose Cycles Ledger account the cycles go to). - The last --json line is printed however the wait ends, not only once the order settles, and says why in `ended`: settled, interrupted, timed_out, contact_lost or not_found. It carries the order as last known (as made, with no status, when the gateway no longer knows it), and gains `expires_at`: until when an unpaid order stays payable. Every key is always present, null where it does not apply. -q still prints the status tag only for a settled order. - The quote's "Wanted:" line says what --cycles asked for without "at least": what is asked for, not a promise, since the gateway's conversion can land a little under the estimate. Extracted from PR #824. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Twxau844tMTwyHpQJEbmBo
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Base:
jd/deploy-shortfall-hint(#823). This branch reuses that one'scommands/args.rs::selection_flags,shell_wordandPasteFlags::notes()for every command it prints, and its message builder (shortfall::Message, whoseprose,commandsandtextthis branch makespub(crate)). The crate keeps one quoting helper and one layout for printed commands. Once #823 merges, this PR retargets tomain.What
These fixes come from the end-to-end test of the 1.7.0 release against mainnet CyclePay, and from the compatibility and docs reviews that went with it. They are CLI-side only:
There are three commits, one per theme. Each builds and passes on its own, and carries its own tests and docs.
1. Arguments and gateway errors
--gateway.get_card_terms(IC0536) is said to be no gateway.canister_not_found) is said not to exist. Any other 4xx stays a failure to read the terms.<--amount <AMOUNT>|--cycles <CYCLES>>. A negative value of either,-1or-5t, is refused by clap in the same words and with exit status 2. Before,--amount -1failed at run time,--cycles -1in clap, and--cycles -5tas an unknown flag-5.-n localoutside a project says there is no such network outside a project, and points at-n ic.2. The prompt and signals
2>&1 | tee buy.log), the command refuses and names--yes. Before, it failed withIO error: not a terminal.create_order, which a stalled network can hold for minutes. The command stops at once and says the order may or may not exist. On CyclePay it also gives the command to find out: asking for the same amount again within the hour hands back the open order rather than making another.Create this order? [y/N]. "Nothing is charged until you pay on Stripe's page." goes on the line above.3. The wait, the messages and
--jsonNo answer from the gateway, retryingonce no poll has been answered for 9 seconds, whether or not a poll is in flight.icp cycles balancefor the cycles, after any unsettled end and after--no-wait;icp canister call <gateway> order_status --query ... '("<id>")'for a refund under way, and for an order an operator handles.selection_flags. The default identity is named, sinceorder_statusanswers only the order's owner and the default can change.Payment pending, not received.detail: why delivery waits, such asRATE_STALE) is shown, cut with…to fit the live line and whole off a terminal.NO_COLOR,TERM=dumb, or colour disabled), the code is 1.7.0's drawing unchanged, so nobody loses what they have today.--cycles. The quote saysWanted: <cycles>over an estimate, notat least.--jsonchanged in one behaviourThe schema is additive.
gatewayandbuyer.endedandexpires_at.The behaviour change. 1.7.0 printed a second line only once the order settled. Now a last line comes however the wait ends, with
endedset tosettled,interrupted,timed_out,contact_lostornot_found.A script written against 1.7.0 that took "a second line" to mean "settled" must read
endedinstead (orstatusand the exit status). Thebuy-cyclesagent skill reads"status":"delivered"plus exit 0, which stays correct. It should mentionendedonce this ships.Follow-ups, for a decision
created_at : opt nat64in the vendoredcycles_gateway.did. CyclePay'sOrderStatusViewhas it, and an optional field stays compatible with any gateway."reused": true|falseto the first JSON line.--cycles. CyclePay converts through ICP e8s, so a delivery can land up to one e8's worth of cycles (about 22k today) under the estimate.least_gross_forrestores "at least X at today's rate".get_icp_xdr_conversion_rate, for CyclePay's gateway only, or be a fixed bound for any gateway (10M cycles covers ICP up to about $1,000).icp cycles buy --cyclessuggestion relies on the buy covering the gap.icp cycles order <id>command, or another way to wait on an existing order;icp cycles balance --jsonwith a plain number;^Cline some terminals leave above the error;... reports 6.938T cycles delivered to <principal>), which runs past 80 columns for a long principal, as in 1.7.0.Tests
Unit: 168 in the
icpbinary at the head. They include:Prompttrait: the stderr check, the catch before the question (a SIGINT raised while it is asked would otherwise end the test binary), and the cursor's return;create_ordergiving way to a ready interrupt;canister_not_found;Integration:
cycles_tests.rsruns 20 tests against a Motoko stand-in gateway, which learns a pending and a refunding state. They cover:order_statuscommand as the buyer;--jsonline on SIGINT;--no-wait;--gateway, and ids that cannot be canisters;-n localoutside a project;Checks, run locally on macOS:
--tests --benches -D warnings), fmt and taplo;icp-project;A frozen network, measured. These ran on the stand-in, under a pty at 80x24, with the local network frozen (SIGSTOP to its pocket-ic) 3 s into the wait.
QR decode matrix.
try_invert=False) and OpenCV 5.0.0 (QRCodeDetector, andQRCodeDetectorArucofor comparison).NO_COLOR=1, and again withTERM=dumb, this branch prints no SGR sequence at all. Its code is 1.7.0's, character for character. As in 1.7.0, it decodes on a dark theme and needs inversion on a light one.qrcodelibrary's own rendering of that link (black on white, a four-module border) at every module size from 6 to 12 px, and passes at 4 and 5 px. zxing-cpp andQRCodeDetectorArucoread every one of them, these drawings included. So the limit is the classic detector's on this version-6 code, not the drawing's.Mainnet, by hand. No run passed
--yesor answered "y": each ended at the prompt (with Ctrl-C), at a refusal, or at an argument or gateway error. No order was made, nothing was paid, and no browser opened. The waits, stalls and refunds ran against the stand-in on a local network.Before and after, in words
Captured under a pty at 80 and 60 columns, in a dark and a light theme, from 1.7.0 and from this branch.
Error: no order created, exit 1....pay on Strip/e. [y/N]). This branch puts "Nothing is charged until you pay on Stripe's page." on its own line, thenCreate this order? [y/N].Error: IO error: not a terminal. This branch refuses and names--yes.interrupted while waiting for order ..., a backticked command broken mid-word at 80 columns. This branch says it once, as the error:order_statuscommand, as the buyer.Payment received; delivering cycles.... This branch showsPayment pending; waiting for it to settle.--gatewayset to the ICP ledger. 1.7.0 prints the replica's IC0536 text over six lines. This branch sayscanister ... is not a cycles gateway: it does not serve get_card_terms, the method a gateway publishes its terms with.--cycles -5t. 1.7.0:unexpected argument '-5' found. This branch:invalid value '-5t' for '--cycles <CYCLES>': cannot be negative..., exit 2.--jsonwith SIGINT during the wait. 1.7.0 prints one line. This branch prints two: the first namesgatewayandbuyer, and the last carries"ended":"interrupted".