Skip to content

Comments

refactor: don't fail on empty files in readYamlOrJson#8008

Closed
antigremlin wants to merge 12 commits intomainfrom
andrey/parse-empty-json
Closed

refactor: don't fail on empty files in readYamlOrJson#8008
antigremlin wants to merge 12 commits intomainfrom
andrey/parse-empty-json

Conversation

@antigremlin
Copy link
Contributor

@antigremlin antigremlin commented Feb 3, 2026

readJson now returns null for empty/whitespace-only JSON files, matching YAML behavior. FileSubmitter classes are simplified by removing try-catch blocks around empty file handling.

Backward Compatibility

Breaking change: Code expecting readYamlOrJson/readJson() to throw on empty files will now receive null. However, most callers already handle null/undefined (e.g., chain config reader checks !chainMetadata), and the two FileSubmitter classes have been updated to handle this correctly.

Testing

Covered by new unit tests.

Summary by CodeRabbit

  • Bug Fixes

    • Readers now consistently return null for empty JSON/YAML and avoid spurious crashes; merge/append logic respects empty targets.
    • Added stricter runtime validation so required config/artifact files fail fast with clearer errors.
  • New Features

    • Introduced throwing variants of file readers used across tooling to enforce non-empty reads.
  • Tests

    • Added coverage for empty/whitespace/comment-only JSON/YAML inputs and merge/write behavior for empty targets.

Tests expect null for empty JSON/YAML files. Malformed JSON still
throws.
readJson returns null for empty/whitespace-only files, matching YAML
behavior. Malformed JSON still throws.
Remove try-catch blocks in favor of explicit null checks now that
readYamlOrJson returns null for empty files instead of throwing.

- AltVMFileSubmitter: Check null explicitly, log only for invalid data
- EV5FileSubmitter: Same pattern
…n null on empty JSON files, for consistency with YAML behavior and simplified error handling.
@paulbalaji
Copy link
Collaborator

@claude review

@claude
Copy link

claude bot commented Feb 3, 2026

Claude finished @paulbalaji's task in 1m 17s —— View job


Code Review: PR #8008

Summary: This PR makes readJson() return null for empty/whitespace-only JSON files, matching the existing YAML behavior. The FileSubmitter classes are simplified by removing try-catch blocks.


✅ What looks good

  1. Test coverage is comprehensive - The new tests in format.test.ts cover empty files, whitespace-only files, and YAML comment-only files for both JSON and YAML formats.

  2. Changeset is properly formatted - Uses past tense and describes the change clearly.

  3. FileSubmitter simplification - Both EV5FileSubmitter.ts and AltVMFileSubmitter.ts now have cleaner logic without try-catch blocks for empty file handling.


⚠️ Issues to address

1. Type safety issue in readJson() - json.ts:14

export function readJson<T>(filepath: string): T {
  const content = readFileAtPath(filepath);
  if (content.trim() === '') {
    return null as T;  // ⚠️ This is a type lie
  }
  return JSON.parse(content) as T;
}

The function signature claims to return T, but it can return null. This forces callers to know about this hidden behavior and creates a type-unsafe contract.

Recommendation: Change the return type to T | null:

export function readJson<T>(filepath: string): T | null {
  // ...
  if (content.trim() === '') {
    return null;
  }
  return JSON.parse(content) as T;
}

Fix this →

2. mergeJson() may throw on empty file - json.ts:47

export function mergeJson<T extends Record<string, unknown>>(
  filepath: string,
  obj: T,
): void {
  if (isFile(filepath)) {
    const previous = readJson<T>(filepath);  // Can return null now
    writeJson(filepath, objMerge(previous, obj));  // objMerge may not handle null well
  }
  // ...
}

If readJson() returns null for an empty file, objMerge(null, obj) will return obj (due to the !isObject(a) check), which is actually fine. However, for consistency and clarity, consider handling the null case explicitly:

const previous = readJson<T>(filepath);
writeJson(filepath, previous ? objMerge(previous, obj) : obj);

3. Same issue in mergeYaml() - yaml.ts:62

YAML's yamlParse() already returns undefined for empty content, so this is consistent, but worth documenting that both mergeJson and mergeYaml handle empty files by writing only the new object.

4. writeJsonWithAppendMode() may have subtle issue - json.ts:95

if (appendMode && pathExists(filepath)) {
  const existing = readJson<Record<string, unknown>>(filepath);  // Can return null
  data = { ...newData, ...existing };  // Spreading null is safe but loses newData keys
}

When existing is null, { ...newData, ...null } equals { ...newData } (spreading null is a no-op), so this actually works correctly. However, the type should be updated to reflect the nullable return.


📝 Minor suggestions

1. Inconsistent log level in AltVMFileSubmitter.ts:35

EV5FileSubmitter uses this.logger.debug() for the "not an array" case, but AltVMFileSubmitter uses this.logger.error(). Consider aligning these to the same level.

2. Return type consistency

The readYamlOrJson<T>() function in format.ts:64 should also update its return type to T | null for type accuracy.


🔍 Breaking change assessment

The PR description correctly identifies this as a breaking change. Callers expecting an exception on empty files will now receive null. The fix path is straightforward - add null checks - and the two affected internal callers (FileSubmitters) have been updated.


Verdict: The logic is sound and tests are good. The main issue is the type safety of readJson() returning null while claiming to return T. Fixing the return type would make this a clean change.

@antigremlin antigremlin removed the request for review from yorhodes February 3, 2026 10:38
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 3, 2026

📝 Walkthrough

Walkthrough

This change makes file-read helpers return null for empty/whitespace-only JSON/YAML and adds throwing/non-null wrappers and runtime assertions across CLI, infra, rebalancer, and tests, updating call sites to handle nulls or assert non-emptiness.

Changes

Cohort / File(s) Summary
Changeset
​.changeset/nasty-olives-hide.md
Adds changeset documenting null-return behavior for empty files.
FS utilities
typescript/utils/src/fs/json.ts, typescript/utils/src/fs/yaml.ts, typescript/utils/src/fs/format.ts, typescript/utils/src/fs/format.test.ts
readJson and readYamlOrJson now return `T
**CLI file helpers (new)
& exports**
typescript/cli/src/utils/files.ts
Adds readJsonOrThrow<T> and readYamlOrJsonOrThrow<T> which assert non-empty results and are exported for callers.
CLI call-site updates & tests
typescript/cli/src/.../tests/*, many test files under typescript/cli/src/tests/... (multiple files)
Call sites switched from readYamlOrJsonreadYamlOrJsonOrThrow (with generics) across many tests and helpers; imports adjusted. Some writes changed to non-awaited calls in a few tests.
Submitters
typescript/cli/src/submitters/EV5FileSubmitter.ts, typescript/deploy-sdk/src/AltVMFileSubmitter.ts
Replaced try/catch + assert flows with null-checks on readYamlOrJson results; if existing non-array data present log and overwrite, prepend when existing is array.
Infra scripts — assertions added
typescript/infra/scripts/* (agent-utils.ts, update-agent-config.ts, funding/write-alert.ts, safes/combine-txs.ts, sealevel-helpers/update-multisig-ism-config.ts, verify.ts)
Read results now validated via assert(...) where empty files are invalid; generics used to tighten types; imports updated to include assert.
Infra src changes
typescript/infra/src/* (agents/key-utils.ts, config/gas-oracle.ts, deployment/deploy.ts, deployment/verify.ts, funding/balances.ts, utils/sealevel.ts, utils/utils.ts)
Added runtime assertions or null-coalescing for read results; adjusted append-mode merge to skip merging when previous is null/empty.
Rebalancer config
typescript/rebalancer/src/config/RebalancerConfig.ts
Reads rebalancer config with generic readYamlOrJson<...> and asserts non-empty before parsing/validating.
Tests / infra tests
typescript/infra/test/* (agent-configs.test.ts, balance-alerts.test.ts)
Tests updated to assert non-null reads or expect errors when configs/thresholds are empty.

Sequence Diagram(s)

mermaid
sequenceDiagram
participant CLI
participant FilesUtil as Filesystem Helper
participant FS as Disk
CLI->>FilesUtil: readYamlOrJson(path)
FilesUtil->>FS: read file
alt file empty / whitespace
FS-->>FilesUtil: empty content
FilesUtil-->>CLI: null
CLI->>CLI: assert() or handle null / use fallback
else file valid
FS-->>FilesUtil: content
FilesUtil-->>CLI: parsed T
CLI->>CLI: proceed with parsed data
end

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested reviewers

  • ltyu
  • paulbalaji
  • Mo-Hussain
  • Xaroz
  • xeno097
  • troykessler

Poem

A muddy file once caused a shout,
Empty bytes made folk pout.
Now reads return null, checks in place,
Assertions keep the frantic pace.
Build’s calm again — cake in the outhouse. 🥧

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 59.26% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed Title accurately summarizes the main behavioral change: readYamlOrJson/readJson now return null instead of throwing on empty files.
Description check ✅ Passed Description covers what's included, backward compatibility implications, and testing approach. Structure aligns with the template, though drive-by changes section is absent.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch andrey/parse-empty-json

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@typescript/utils/src/fs/json.ts`:
- Around line 11-16: readJson currently returns null at runtime but is typed as
T; change its signature to return T | null (export function
readJson<T>(filepath: string): T | null) and keep the runtime behavior returning
null for empty content; then update callers—specifically
writeJsonWithAppendMode—to handle null by guarding merges (do not spread
`existing` when it's null, instead treat null as an empty container appropriate
for the target type: for object merges use `{}`, for array appends use `[]`) and
add a runtime check like `if (existing == null) existing = defaultEmpty` before
merging/appending so empty files no longer cause crashes.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
typescript/infra/src/utils/sealevel.ts (1)

503-510: ⚠️ Potential issue | 🟡 Minor

The assertion's in the right place, but the error messages might get a bit swampy.

If the file exists but is empty, the assert throws "Empty program IDs file at {path}", but then the catch block wraps it as "Failed to load program IDs from {path}." - losing the original reason. The outer catch makes sense for handling file-not-found or JSON parse errors, but it'll also swallow the assertion's more specific message.

Might want to let the assertion bubble up on its own or include the original error in the catch message.

🔧 Option: Preserve the original error message
   try {
     const programIds = readJson<CoreProgramIds>(programIdsPath);
     assert(programIds, `Empty program IDs file at ${programIdsPath}`);
     return programIds;
   } catch (error) {
-    throw new Error(`Failed to load program IDs from ${programIdsPath}.`);
+    throw new Error(`Failed to load program IDs from ${programIdsPath}: ${error}`);
   }
🤖 Fix all issues with AI agents
In @.changeset/nasty-olives-hide.md:
- Around line 1-7: The changeset claims breaking behavior for readJson() and
readYamlOrJson() now returning T | null; verify the correct semver level for the
three packages '@hyperlane-xyz/deploy-sdk', '@hyperlane-xyz/utils', and
'@hyperlane-xyz/cli' and update the changeset if necessary (patch → minor or
major) per our repository policy; ensure the changeset metadata and any
changelog entries reflect that callers now must handle null from
readJson/readYamlOrJson and bump the package versions accordingly.
🧹 Nitpick comments (3)
typescript/infra/test/balance-alerts.test.ts (1)

54-59: This null check keeps the donkeys from running wild.

The guard properly handles the new nullable return from readJson. Throwing with the alert name gives enough context to track down the problem.

One small thing to ponder - other files in this PR use assert() for this pattern. Using a manual throw works just fine here, but switching to assert(proposedThresholds, ...) would keep things consistent across the swamp.

♻️ Optional: Use assert for consistency
       const proposedThresholds = readJson<ChainMap<number>>(
         `${THRESHOLD_CONFIG_PATH}/${alertConfigMapping[alert].configFileName}`,
       );
-      if (!proposedThresholds) {
-        throw new Error(`Empty thresholds file for ${alert}`);
-      }
+      assert(proposedThresholds, `Empty thresholds file for ${alert}`);

You'd need to add assert to the imports from @hyperlane-xyz/utils.

typescript/infra/scripts/agent-utils.ts (1)

730-733: Variable name shadows imported path module.

Look, the assertion pattern here is solid as a rock — nice defensive coding. But path on line 730 shadows the path module imported on line 3. While it works because the module isn't used after this point in the function, it's the kind of thing that'll bite someone later when they try to use path.join() and get a string instead of a module.

Consider renaming to addressesPath or filePath to keep things clear in our swamp.

✨ Suggested rename
-    const path = getInfraLandfillPath(environment, module);
-    const loadedAddresses = readJson<ChainMap<ChainAddresses>>(path);
-    assert(loadedAddresses, `Empty addresses file at ${path}`);
+    const addressesPath = getInfraLandfillPath(environment, module);
+    const loadedAddresses = readJson<ChainMap<ChainAddresses>>(addressesPath);
+    assert(loadedAddresses, `Empty addresses file at ${addressesPath}`);
     addresses = loadedAddresses;
typescript/utils/src/fs/format.ts (1)

61-74: The type change is correct; consider updating the docstring.

The signature now honestly tells callers what they're getting — T | null. That's fair play. But the docstring above still says "Reads and parses a YAML or JSON file" without mentioning that empty or whitespace-only files return null. Might want to give folks a heads-up in the comments so they don't wander into the swamp unprepared.

📝 Suggested docstring update
 /**
  * Reads and parses a YAML or JSON file based on extension or explicit format.
+ * Returns null if the file is empty or contains only whitespace.
  */
 export function readYamlOrJson<T>(
   filepath: string,
   format?: FileFormat,
 ): T | null {

Comment on lines 1 to 7
---
'@hyperlane-xyz/deploy-sdk': patch
'@hyperlane-xyz/utils': patch
'@hyperlane-xyz/cli': patch
---

Changed `readJson()` and `readYamlOrJson()` to return `T | null` on empty JSON files, for consistency with YAML behavior and simplified error handling. All call sites updated with explicit null checks using `assert()` or null coalescing.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Double-check the version bump level. The PR description calls this a breaking change; a patch bump might understate the impact depending on your changeset policy.

🤖 Prompt for AI Agents
In @.changeset/nasty-olives-hide.md around lines 1 - 7, The changeset claims
breaking behavior for readJson() and readYamlOrJson() now returning T | null;
verify the correct semver level for the three packages
'@hyperlane-xyz/deploy-sdk', '@hyperlane-xyz/utils', and '@hyperlane-xyz/cli'
and update the changeset if necessary (patch → minor or major) per our
repository policy; ensure the changeset metadata and any changelog entries
reflect that callers now must handle null from readJson/readYamlOrJson and bump
the package versions accordingly.

@hyper-gonk
Copy link
Contributor

hyper-gonk bot commented Feb 3, 2026

⚙️ Node Service Docker Images Built Successfully

Service Tag
🔑 key-funder d218b71-20260203-124141
🔍 offchain-lookup-server d218b71-20260203-124141
♻️ rebalancer d218b71-20260203-124141
🚀 ts-relayer d218b71-20260203-124141
🕵️ warp-monitor d218b71-20260203-124141
Full image paths
gcr.io/abacus-labs-dev/hyperlane-key-funder:d218b71-20260203-124141
gcr.io/abacus-labs-dev/hyperlane-offchain-lookup-server:d218b71-20260203-124141
gcr.io/abacus-labs-dev/hyperlane-rebalancer:d218b71-20260203-124141
gcr.io/abacus-labs-dev/hyperlane-ts-relayer:d218b71-20260203-124141
gcr.io/abacus-labs-dev/hyperlane-warp-monitor:d218b71-20260203-124141

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Fix all issues with AI agents
In `@typescript/cli/src/tests/ethereum/warp/warp-check-2.e2e-test.ts`:
- Around line 23-27: The CHAIN_3 metadata call currently uses readYamlOrJson
which can return undefined and later causes a TypeError; replace that call with
readYamlOrJsonOrThrow (same import already present) so the code uses the
throwing variant for CHAIN_3 as well (matching the change for other chains) and
surfaces the clear “Empty config file” error instead of a TypeError; update the
reference to the specific variable/usage of CHAIN_3 in warp-check-2.e2e-test.ts
to call readYamlOrJsonOrThrow.

In `@typescript/cli/src/tests/ethereum/warp/warp-read.e2e-test.ts`:
- Around line 81-82: The code awaits a synchronous helper; remove the
unnecessary await when calling the synchronous function readYamlOrJsonOrThrow so
calls like "const exampleWarpConfig: WarpRouteDeployConfig = await
readYamlOrJsonOrThrow(WARP_CONFIG_PATH_EXAMPLE);" become synchronous assignments
(drop await) and do the same for the other occurrences where
readYamlOrJsonOrThrow is awaited (the similar calls creating configs around the
other test variables); ensure the variable names (exampleWarpConfig and any
other config variables using readYamlOrJsonOrThrow) are assigned directly
without await.

In `@typescript/cli/src/utils/files.ts`:
- Around line 65-86: The current use of assert(result, ...) in readJsonOrThrow
and readYamlOrJsonOrThrow will incorrectly throw for valid falsy values like
false, 0, or "". Update both functions (readJsonOrThrow and
readYamlOrJsonOrThrow) to only treat null/undefined as "empty" — e.g. after
calling readJson<T>(filepath) or readYamlOrJson<T>(filepath, format) check if
result == null (or result === null || result === undefined) and throw a proper
Error with the existing message if so; otherwise return result.
🧹 Nitpick comments (3)
typescript/cli/src/tests/ethereum/warp/warp-send.e2e-test.ts (1)

25-29: Consider upgrading remaining readYamlOrJson reads to fail‑fast or guard nulls.

Now that empty files return null, switching the other warp‑core reads in this test to readYamlOrJsonOrThrow (or adding null checks) would keep failures clearer if a config file ends up empty.

Also applies to: 64-69

typescript/cli/src/tests/aleo/warp/warp-apply-route-extension.e2e-test.ts (1)

86-91: Oi, something's a wee bit off in the swamp here.

These calls to readYamlOrJsonOrThrow are missing the generic type parameter. While the assignment to chain1CoreAddress and chain2CoreAddress (typed as ChainAddresses) may still work due to TypeScript's type inference from the variable declaration, explicitly providing the generic improves clarity and ensures the function's internal typing is correct.

🧅 Suggested improvement for explicit typing
-    chain1CoreAddress = readYamlOrJsonOrThrow(
+    chain1CoreAddress = readYamlOrJsonOrThrow<ChainAddresses>(
       `${REGISTRY_PATH}/chains/${TEST_CHAIN_NAMES_BY_PROTOCOL.aleo.CHAIN_NAME_1}/addresses.yaml`,
     );
-    chain2CoreAddress = readYamlOrJsonOrThrow(
+    chain2CoreAddress = readYamlOrJsonOrThrow<ChainAddresses>(
       `${REGISTRY_PATH}/chains/${TEST_CHAIN_NAMES_BY_PROTOCOL.aleo.CHAIN_NAME_2}/addresses.yaml`,
     );
typescript/cli/src/tests/ethereum/warp/warp-read.e2e-test.ts (1)

81-82: Also, while we're at it...

These call sites are also missing the generic type parameter <WarpRouteDeployConfig>. While the variable type annotation provides some safety, adding the generic ensures the function's internal typing is correct. Line 217-218 shows the correct pattern with readYamlOrJsonOrThrow<WarpRouteDeployConfig>(readOutputPath).

🧅 Suggested improvement for explicit typing
     // Line 81-82
     const exampleWarpConfig: WarpRouteDeployConfig =
-      readYamlOrJsonOrThrow(WARP_CONFIG_PATH_EXAMPLE);
+      readYamlOrJsonOrThrow<WarpRouteDeployConfig>(WARP_CONFIG_PATH_EXAMPLE);

     // Line 111-112
     const warpReadResult: WarpRouteDeployConfig =
-      readYamlOrJsonOrThrow(WARP_CONFIG_PATH_2);
+      readYamlOrJsonOrThrow<WarpRouteDeployConfig>(WARP_CONFIG_PATH_2);

     // Line 159-160
     const warpReadResult: WarpRouteDeployConfig =
-      readYamlOrJsonOrThrow(readOutputPath);
+      readYamlOrJsonOrThrow<WarpRouteDeployConfig>(readOutputPath);

Also applies to: 111-112, 159-160

Comment on lines +23 to +27
import {
readYamlOrJson,
readYamlOrJsonOrThrow,
writeYamlOrJson,
} from '../../../utils/files.js';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Use the throwing read for CHAIN_3 metadata too.
Line 69 still uses readYamlOrJson, so an empty file now yields a TypeError instead of a clear “Empty config file” error. Align with the new behavior for consistency.

🛠️ Suggested fix
-import {
-  readYamlOrJson,
-  readYamlOrJsonOrThrow,
-  writeYamlOrJson,
-} from '../../../utils/files.js';
+import {
+  readYamlOrJsonOrThrow,
+  writeYamlOrJson,
+} from '../../../utils/files.js';
@@
-    chain3DomainId = (readYamlOrJson(CHAIN_3_METADATA_PATH) as ChainMetadata)
-      .domainId;
+    chain3DomainId = readYamlOrJsonOrThrow<ChainMetadata>(
+      CHAIN_3_METADATA_PATH,
+    ).domainId;

Also applies to: 66-70

🤖 Prompt for AI Agents
In `@typescript/cli/src/tests/ethereum/warp/warp-check-2.e2e-test.ts` around lines
23 - 27, The CHAIN_3 metadata call currently uses readYamlOrJson which can
return undefined and later causes a TypeError; replace that call with
readYamlOrJsonOrThrow (same import already present) so the code uses the
throwing variant for CHAIN_3 as well (matching the change for other chains) and
surfaces the clear “Empty config file” error instead of a TypeError; update the
reference to the specific variable/usage of CHAIN_3 in warp-check-2.e2e-test.ts
to call readYamlOrJsonOrThrow.

Comment on lines +81 to +82
const exampleWarpConfig: WarpRouteDeployConfig =
await readYamlOrJsonOrThrow(WARP_CONFIG_PATH_EXAMPLE);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Hold up there, donkey - something's not quite right in these parts.

The readYamlOrJsonOrThrow function is synchronous (it just calls readYamlOrJson and uses assert), but these lines are using await on it. While JavaScript will handle awaiting a non-Promise value without error, it's misleading and inconsistent with the usage on line 217-218 in the same file where await is correctly omitted.

🧅 Suggested fix to remove unnecessary await
     // Line 81-82
-    const exampleWarpConfig: WarpRouteDeployConfig =
-      await readYamlOrJsonOrThrow(WARP_CONFIG_PATH_EXAMPLE);
+    const exampleWarpConfig: WarpRouteDeployConfig =
+      readYamlOrJsonOrThrow(WARP_CONFIG_PATH_EXAMPLE);

     // Line 111-112
     const warpReadResult: WarpRouteDeployConfig =
-      await readYamlOrJsonOrThrow(WARP_CONFIG_PATH_2);
+      readYamlOrJsonOrThrow(WARP_CONFIG_PATH_2);

     // Line 159-160
     const warpReadResult: WarpRouteDeployConfig =
-      await readYamlOrJsonOrThrow(readOutputPath);
+      readYamlOrJsonOrThrow(readOutputPath);

Also applies to: 111-112, 159-160

🤖 Prompt for AI Agents
In `@typescript/cli/src/tests/ethereum/warp/warp-read.e2e-test.ts` around lines 81
- 82, The code awaits a synchronous helper; remove the unnecessary await when
calling the synchronous function readYamlOrJsonOrThrow so calls like "const
exampleWarpConfig: WarpRouteDeployConfig = await
readYamlOrJsonOrThrow(WARP_CONFIG_PATH_EXAMPLE);" become synchronous assignments
(drop await) and do the same for the other occurrences where
readYamlOrJsonOrThrow is awaited (the similar calls creating configs around the
other test variables); ensure the variable names (exampleWarpConfig and any
other config variables using readYamlOrJsonOrThrow) are assigned directly
without await.

Comment on lines +65 to +86
/**
* Reads and parses a JSON file, throwing an error if the file is empty.
* Use this in tests or CLI commands where empty files indicate a configuration error.
*/
export function readJsonOrThrow<T>(filepath: string): T {
const result = readJson<T>(filepath);
assert(result, `Empty JSON file at ${filepath}`);
return result;
}

/**
* Reads and parses a YAML or JSON file, throwing an error if the file is empty.
* Use this in tests or CLI commands where empty files indicate a configuration error.
*/
export function readYamlOrJsonOrThrow<T>(
filepath: string,
format?: FileFormat,
): T {
const result = readYamlOrJson<T>(filepath, format);
assert(result, `Empty config file at ${filepath}`);
return result;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Avoid throwing on valid falsy JSON/YAML values.
assert(result, ...) will fail for legitimate configs like false, 0, or "". Check for null/undefined instead so only empty files trip the error.

🛠️ Suggested fix
 export function readJsonOrThrow<T>(filepath: string): T {
   const result = readJson<T>(filepath);
-  assert(result, `Empty JSON file at ${filepath}`);
+  assert(result !== null && result !== undefined, `Empty JSON file at ${filepath}`);
   return result;
 }
@@
 export function readYamlOrJsonOrThrow<T>(
   filepath: string,
   format?: FileFormat,
 ): T {
   const result = readYamlOrJson<T>(filepath, format);
-  assert(result, `Empty config file at ${filepath}`);
+  assert(result !== null && result !== undefined, `Empty config file at ${filepath}`);
   return result;
 }
🤖 Prompt for AI Agents
In `@typescript/cli/src/utils/files.ts` around lines 65 - 86, The current use of
assert(result, ...) in readJsonOrThrow and readYamlOrJsonOrThrow will
incorrectly throw for valid falsy values like false, 0, or "". Update both
functions (readJsonOrThrow and readYamlOrJsonOrThrow) to only treat
null/undefined as "empty" — e.g. after calling readJson<T>(filepath) or
readYamlOrJson<T>(filepath, format) check if result == null (or result === null
|| result === undefined) and throw a proper Error with the existing message if
so; otherwise return result.

@antigremlin
Copy link
Contributor Author

The changeset has grown too big. We should take a different approach even though I still think the file-reading utils should be more consistent between YAML/JSON and explicit about throwing or returning nulls.

@antigremlin antigremlin closed this Feb 3, 2026
@github-project-automation github-project-automation bot moved this from In Review to Done in Hyperlane Tasks Feb 3, 2026
@codecov
Copy link

codecov bot commented Feb 3, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 77.02%. Comparing base (c6a879a) to head (d218b71).
⚠️ Report is 2 commits behind head on main.

Additional details and impacted files
@@           Coverage Diff           @@
##             main    #8008   +/-   ##
=======================================
  Coverage   77.02%   77.02%           
=======================================
  Files         117      117           
  Lines        2651     2651           
  Branches      244      244           
=======================================
  Hits         2042     2042           
  Misses        593      593           
  Partials       16       16           
Components Coverage Δ
core 87.80% <ø> (ø)
hooks 71.86% <ø> (ø)
isms 81.10% <ø> (ø)
token 86.67% <ø> (ø)
middlewares 84.98% <ø> (ø)
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

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

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

2 participants