Skip to content

Build1 publisher2 min readPublished

Passing 1 instead of 2 makes a Korean debarment API return HTTP 200 and no records for every company

One developer found that passing inqryDiv=1 instead of 2 to a Korean debarment API returns HTTP 200 and no records for every company. Only a test against a company already known to be sanctioned caught it before the feature shipped.

The Engineer · Build desk

Illustration accompanying Passing 1 instead of 2 makes a Korean debarment API return HTTP 200 and no records for every company

What happened

  • The bug surfaced while the developer was adding debarment records, public sanctions barring companies from public procurement, to a business verification API.
  • A scheduled Cloud Run job, run once by hand before trusting Cloud Scheduler, exited 0 in under a second with a green console check while doing nothing at all.
  • Searches for Korean company names over curl in Git Bash returned zero results because the shell mangled the URL's UTF-8, while a local Node script got correct results.

Compiled by The EngineerSomething wrong?How this is made

Why it matters

  • exposure Shipped, the wrong value would have told users 'no sanctions found' for every company in Korea, and alerting keyed on error rates would never fire because each of those responses is a 200.
  • constraint A scheduled job needs a post-run check on its output, such as records enriched since the last run, because the platform shows the same success for a working job and one whose main() never ran.
  • decision Lookup tests now need a fixture record known to be positive, since a clean sample passes under both parameter values; the author wrote that rule into the project docs.

An empty result set is also the correct answer for most companies. A clean company queried the right way and a sanctioned company queried the wrong way get the same response: HTTP 200, no rows, no warning [2][5]. The inqryDiv value picks which kind of query the endpoint runs. With 1, the developer wrote, the API "answers a question nobody asked and returns nothing, forever, for every company" [3]. "That's not a broken feature. That's a feature that confidently lies," the developer wrote [6].

The Cloud Run failure sits in one line of the entry point. To detect whether the module was being run directly, the code compared `import.meta.url` with a hand-built string, `file:///${process.argv[1]}` [8]. On Windows, `process.argv[1]` looks like `C:\path\to\file.js`, and the prefix yields a valid file URL [8]. On Linux it is already `/app/dist/job.js`, so the string comes out as `file:////app/dist/job.js` [8]. Four slashes never match. The guard evaluated false, `main()` never ran, and the process exited cleanly [8]. The comment above the check in the post reads "Works on my machine. Not on Linux." [18]

The fix is one line, `pathToFileURL(process.argv[1]).href` [9]. It is the right fix. It hands path-to-URL conversion to a function that knows each platform's path format, so no string concatenation is left to break on the next operating system. The developer wrote that "a green checkmark in a scheduler dashboard is not evidence that your code ran," and added: "It's evidence that a container started and stopped without crashing." [10] Trusting the schedule would have meant a week of green checks over an untouched index, and a search through the API, the credentials and the data before anyone looked at the start-up guard [11].

Git Bash cost the least time. Before trying another client, the developer was minutes from digging into the Korean text normalization pipeline, a plausible suspect given the encoding pitfalls of Korean text [13]. The post's lesson: "when a test fails, the test environment is a suspect too." [19]

"Every layer answered truthfully. None of them answered my question," the developer wrote of all three [17]. According to the post, this is the failure mode that "costs the most time" [14]. That ranking rests on one developer's three bugs, and none of them reached a customer [4][11]. The paid API behind them had two real calls in a week, according to the author's earlier post [15]. So the cost on record is debugging time and one near miss. The post does not estimate what a shipped false negative on a sanctions check would cost.

What to watch

  • Whether the operator of the debarment endpoint starts rejecting invalid inqryDiv values with an error instead of an empty 200.
  • Whether the author's verification API adds a standing known-positive check on its debarment data as paid traffic grows past two calls a week.
Loading claim ledger
Loading source directory links
Loading share composer
Loading topic controls
Loading related stories