Send POST and PUT parameters in the request body #241 - #242
Conversation
trello.js puts every write parameter in the query string, and Trello rejects URLs over roughly 8KB with a 414. Percent-encoding expands each CJK character to nine, so a description of around 900 Chinese characters is already enough to fail — the update is rejected outright and the card is left unchanged. Wrap the TrelloClient instance so POST and PUT parameters travel in a JSON body instead. Requests that already carry a body are skipped, so the multipart file upload and the endpoints trello.js models with `data` are untouched, and key/token stay in the query string. Verified against the live API: 16 write endpoints accept parameters supplied only in the body, including required ones, and native JSON arrays, booleans, numbers and date strings parse identically to their query-string form. End to end with a 10,014-byte CJK description, card:update goes from exiting 1 with a 414 to storing all 3,338 characters byte-for-byte. This has to wrap the instance rather than subclass TrelloClient: the suite mocks trello.js with a constructor returning a plain object, which discards a subclass prototype and makes an overridden sendRequest unreachable. Wrapping leaves all 28 existing mocked test files unchanged. Fixes long text on card:update --description, card:create --description, card:comment --text, board:create --description, and the TUI description editor. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
CI pins pnpm 8 while pnpm-lock.yaml is lockfileVersion 9.0, so pnpm logs "Ignoring not compatible lockfile" and re-resolves every dependency from its semver range. @oclif/core: ^4.8.0 therefore floats, and 4.13.4 ships a module format Jest cannot parse under the current CommonJS setup, so 29 of the 42 suites fail to run before any assertion executes. Bisected all 35 releases between 4.8.0 and 4.13.5: 4.13.3 is the last one that works. A control run on main without this branch's changes fails identically (29 suites, 135 tests), which is what identifies the breakage as pre-existing rather than caused by the request-body change. Pinning the direct dependency is enough on its own — @oclif/test takes @oclif/core as a peer and the three @oclif/plugin-* ranges all accept 4.13.3, so a clean pnpm 8 install resolves a single copy. Verified under pnpm 8 (lockfile ignored, as CI does) and pnpm 10 (lockfile honoured): 42 suites / 316 tests green in both. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
@mheap — whenever you have a moment, would you mind casting an eye over this? No urgency at all. One thing worth flagging up front so it doesn't look odd: the second commit pins The fix itself is the first commit; background and the API verification behind it are in #241. Checks are green on all four Node versions. Happy to split this up, rework it, or drop the pin — whichever suits you best. Thanks for maintaining this. |
#241
TL;DR
POSTandPUTparameters now travel in a JSON request body instead of the query string, so long descriptions and comments stop failing withHTTP 414. Background and the API verification are in the linked issue.It also pins
@oclif/coreto4.13.3. CI is red onmainfor an unrelated reason, and no PR can go green without it. That change is a separate commit and can be dropped on its own.Behaviour change
card:update --descriptionwith 10,014 bytes of CJK414; description unchangedPOST/PUTparametersGET,DELETE, multipart upload,key/token23 commands issue
POSTorPUT, so all of them move to the new transport. Five were failing on long text:card:update --description,card:create --description,card:comment --text,board:create --description, and the TUI's multi-line description editor.The @oclif/core pin
Why CI is red, independent of this branch:
pnpm-lock.yamlislockfileVersion: '9.0'. pnpm 8 logsWARN Ignoring not compatible lockfileand re-resolves every dependency from its semver range, so CI has never installed the locked versions.@oclif/core: ^4.8.0therefore floats.4.13.4ships a module format Jest cannot parse under the current CommonJS setup, so 29 of the 42 suites fail withJest encountered an unexpected tokenbefore any assertion runs.Three runs on clean checkouts with pnpm 8, matching what CI installs:
@oclif/coremainatffb7c214.13.54.13.54.13.3Rows one and two reproduce CI's numbers exactly and are identical — that is what identifies the breakage as pre-existing. The 13-test difference is this PR's own tests, all passing.
The boundary came from a binary search over all 35 releases between
4.8.0and4.13.5, with both sides re-run explicitly:4.13.3passes 42 suites,4.13.4fails 29.4.13.5is the newest 4.x, so4.13.3is the newest version that works.One more run checked the pin as it actually ships — only the
package.jsonchange, nopnpm.overrides, clean pnpm 8 install. One copy of@oclif/core@4.13.3is installed and the suite is green. Pinning the direct dependency suffices because@oclif/testtakes@oclif/coreas a peer and the three@oclif/plugin-*ranges accept4.13.3.The pin is temporary. It can go once either cause is addressed: raise the workflow's pnpm to match the lockfile, or teach Jest to handle the newer module format. Two caveats if you take the first route:
posttest: "yarn lint"has never run in CI, because pnpm 8 skipspostscripts, and it currently reports 132 errors — all in.tsxfiles, whicheslint.config.mjsleaves out of its rule overrides. AndVersion PackagesPRs opened byGITHUB_TOKENget no checks at all.Risk and reviewer focus
1. The pin is a policy call, not a bug fix. An exact pin stops consumers receiving patch releases. If you would rather fix the pnpm/lockfile mismatch, or use a range like
>=4.8.0 <4.13.4, say so — the 414 fix does not depend on it.2. The wrapper reaches into a dependency's request pipeline. It replaces
sendRequeston theTrelloClientinstance, so it is coupled to that method's signature and to two fields ofRequestConfig. Upstream is not an alternative here:trello.js2.1.6 still putsdescin the query string, is ESM-only, and requires Node >= 22.3. Every
POST/PUTchanges transport, not just the broken ones. 17 write endpoints were checked against the live API with all parameters — including required ones — sent only in the body; all applied correctly. Only two of them document body support. The gap isPUT /notifications/{id}, which returns401for body and query string on every notification available for testing, sonotification:readandnotification:unreadare unverified rather than confirmed.4. Values now travel as native JSON types instead of percent-encoded strings. Arrays, comma-joined strings, booleans, numbers, ISO dates, and the native
Datestringchrono-nodeproduces for--duewere each verified to parse to the same result as before. Object-valued parameters were inconclusive; no command sends one, and a code comment says so.5. Instance wrapping instead of subclassing is deliberate.
jest.mock("trello.js")returns a plain object from the constructor, which discards a subclass prototype and makes an overriddensendRequestunreachable. Wrapping keeps all 28 mocked test files unchanged.Walkthrough
src/paramsInBody.tsmoveParamsIntoBody(config)is pure: untouched config unless the method isPOSTorPUT,datais absent, and at least one parameter is non-empty; otherwise a new config withparamsemptied and the survivors asdata.undefinedandnullare dropped, matching the query serialiser —updateCardpasses all 17 fields on every call, most of themundefined.paramsbecomes{}rather than being deleted, becauseBaseClient.sendRequestspreads it and appendskeyandtokenafterwards, so credentials stay in the query string without the wrapper knowing them.sendParamsInBody(client)installs the transform as an own property on the instance. The API groups hold a reference to the client and callthis.client.sendRequest(...), so one own property covers every call site. The cast exists only becausesendRequestis overloaded.src/BaseCommand.ts,src/commands/interactive.tsxThe only two places that call
new TrelloClient(...)—BaseCommand.initfor regular commands, andinteractive.tsxfor the TUI. Both wrap at construction, which is why the TUI is covered.test/paramsInBody.test.ts— 13 testsEleven for the transform:
POSTandPUTrelocation, a long CJK description staying out of the query string, lowercase methods,GETandDELETEuntouched, a request that already carries a body untouched, empty-value stripping, the no-usable-params and no-params cases, and no mutation of the input.Two for the installer: that
sendRequestis routed through the transform and its result returned, and that the callback argument passes through.package.json,pnpm-lock.yaml@oclif/coremoves from^4.8.0to4.13.3, lockfile regenerated to match. The lockfile diff is confined to that dependency.Conclusion
The 414 fix is transport-only: no flag, output format or exit code moves. Full suite green — 42 suites / 316 tests,
tsc --noEmitclean, no new eslint errors — under both pnpm 10 and pnpm 8.🤖 Generated with Claude Code