Skip to content

assets pull hangs forever at 100% CPU when the Build API returns an error #125

Description

@jayenashar

What happens

flex-cli assets pull never returns when the Build API answers the pull endpoint with an error. It prints two exceptions and then reprints its download progress line every 100ms indefinitely, holding a core at 100%. It has to be killed; SIGTERM is enough.

Observed output, trimmed:

No matching clause: application/edn
Cannot read properties of null (reading 'on')
Downloaded 0.00MBDownloaded 0.00MBDownloaded 0.00MB…   (forever)

Why

Three things line up, all on master at 4480e69:

  1. pull-assets asks for zip: (do-get api-client "/assets/pull" query-params {::api.client/accept "application/zip"}) in src/sharetribe/flex_cli/commands/assets.cljs.
  2. parse-body in src/sharetribe/flex_cli/api/client.cljs dispatches on the response content type with a case that has clauses for "text/plain"/nil, "application/transit+json" and "application/zip", and no default. The Build API answers errors on this endpoint in application/edn, so every error response throws No matching clause: application/edn.
  3. That failure does not stop pull-assets from reaching print-progress!, which then receives a null stream: the second message, Cannot read properties of null (reading 'on'), is (.on stream "data" ...) on null. print-progress! creates its setInterval before registering handlers, and clearInterval only ever runs in the "end" handler, which was never registered. The orphaned interval keeps the Node event loop alive and keeps printing.

So the unparseable content type is the trigger, and the leaked interval is what turns a crash into a hang.

Reproduction, no credentials needed

The stub ignores the API key, so any logged-in state works.

// edn-403.mjs
import http from 'node:http';
const body = '{:errors [{:status 403, :code :forbidden, :title "Forbidden"}]}';
http.createServer((req, res) => {
  res.writeHead(403, { 'Content-Type': 'application/edn' });
  res.end(body);
}).listen(18713, '127.0.0.1');
node edn-403.mjs &
FLEX_API_BASE_URL=http://127.0.0.1:18713/v1/build-api \
  flex-cli assets pull -m any-marketplace --path ./assets

It never returns. Verified against flex-cli 1.17.1 installed from npm.

How this shows up in practice

GET /assets/pull currently answers 403 Forbidden, in edn, for our API key on every marketplace we can reach, while POST /assets/push on the same key gets as far as validation and answers 400 validation-invalid-value. So an ordinary permission error is enough to hang the CLI indefinitely. Whatever the right answer is on the permission side, the CLI should report the error and exit.

Suggested fix

Either change alone stops the hang:

  • Give parse-body's case a default clause, so an unexpected content type becomes a reported error instead of No matching clause. Handling "application/edn" next to the transit clause would additionally let the existing error page render the :title from the body.
  • In print-progress!, start the interval only after the handlers are attached, or clear it if attaching fails, so nothing can leave a bare setInterval holding the event loop open.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions