diff --git a/__tests__/main.test.ts b/__tests__/main.test.ts
index b57248757..36024e650 100644
--- a/__tests__/main.test.ts
+++ b/__tests__/main.test.ts
@@ -101,6 +101,7 @@ describe('main tests', () => {
       ${'  14.1.0  '}                              | ${'14.1.0'}
       ${'{"volta": {"node": ">=14.0.0 <=17.0.0"}}'}| ${'>=14.0.0 <=17.0.0'}
       ${'{"engines": {"node": "17.0.0"}}'}         | ${'17.0.0'}
+      ${'{}'}                                      | ${null}
     `.it('parses "$contents"', ({contents, expected}) => {
       expect(util.parseNodeVersionFile(contents)).toBe(expected);
     });
diff --git a/dist/cache-save/index.js b/dist/cache-save/index.js
index 67f22cd3c..ece5ae39a 100644
--- a/dist/cache-save/index.js
+++ b/dist/cache-save/index.js
@@ -83338,9 +83338,25 @@ function parseNodeVersionFile(contents) {
     let nodeVersion;
     // Try parsing the file as an NPM `package.json` file.
     try {
-        nodeVersion = (_a = JSON.parse(contents).volta) === null || _a === void 0 ? void 0 : _a.node;
-        if (!nodeVersion)
-            nodeVersion = (_b = JSON.parse(contents).engines) === null || _b === void 0 ? void 0 : _b.node;
+        const manifest = JSON.parse(contents);
+        // JSON can parse numbers, but that's handled later
+        if (typeof manifest === 'object') {
+            nodeVersion = (_a = manifest.volta) === null || _a === void 0 ? void 0 : _a.node;
+            if (!nodeVersion)
+                nodeVersion = (_b = manifest.engines) === null || _b === void 0 ? void 0 : _b.node;
+            // if contents are an object, we parsed JSON
+            // this can happen if node-version-file is a package.json
+            // yet contains no volta.node or engines.node
+            //
+            // if node-version file is _not_ json, control flow
+            // will not have reached these lines.
+            //
+            // And because we've reached here, we know the contents
+            // *are* JSON, so no further string parsing makes sense.
+            if (!nodeVersion) {
+                return null;
+            }
+        }
     }
     catch (_d) {
         core.info('Node version file is not JSON file');
diff --git a/dist/setup/index.js b/dist/setup/index.js
index 0512f3a79..c4b448b1d 100644
--- a/dist/setup/index.js
+++ b/dist/setup/index.js
@@ -93728,7 +93728,13 @@ function resolveVersionInput() {
         if (!fs_1.default.existsSync(versionFilePath)) {
             throw new Error(`The specified node version file at: ${versionFilePath} does not exist`);
         }
-        version = (0, util_1.parseNodeVersionFile)(fs_1.default.readFileSync(versionFilePath, 'utf8'));
+        const parsedVersion = (0, util_1.parseNodeVersionFile)(fs_1.default.readFileSync(versionFilePath, 'utf8'));
+        if (parsedVersion) {
+            version = parsedVersion;
+        }
+        else {
+            core.warning(`Could not determine node version from ${versionFilePath}. Falling back`);
+        }
         core.info(`Resolved ${versionFileInput} as ${version}`);
     }
     return version;
@@ -93783,9 +93789,25 @@ function parseNodeVersionFile(contents) {
     let nodeVersion;
     // Try parsing the file as an NPM `package.json` file.
     try {
-        nodeVersion = (_a = JSON.parse(contents).volta) === null || _a === void 0 ? void 0 : _a.node;
-        if (!nodeVersion)
-            nodeVersion = (_b = JSON.parse(contents).engines) === null || _b === void 0 ? void 0 : _b.node;
+        const manifest = JSON.parse(contents);
+        // JSON can parse numbers, but that's handled later
+        if (typeof manifest === 'object') {
+            nodeVersion = (_a = manifest.volta) === null || _a === void 0 ? void 0 : _a.node;
+            if (!nodeVersion)
+                nodeVersion = (_b = manifest.engines) === null || _b === void 0 ? void 0 : _b.node;
+            // if contents are an object, we parsed JSON
+            // this can happen if node-version-file is a package.json
+            // yet contains no volta.node or engines.node
+            //
+            // if node-version file is _not_ json, control flow
+            // will not have reached these lines.
+            //
+            // And because we've reached here, we know the contents
+            // *are* JSON, so no further string parsing makes sense.
+            if (!nodeVersion) {
+                return null;
+            }
+        }
     }
     catch (_d) {
         core.info('Node version file is not JSON file');
diff --git a/src/main.ts b/src/main.ts
index ac0517665..34f943104 100644
--- a/src/main.ts
+++ b/src/main.ts
@@ -105,7 +105,17 @@ function resolveVersionInput(): string {
       );
     }
 
-    version = parseNodeVersionFile(fs.readFileSync(versionFilePath, 'utf8'));
+    const parsedVersion = parseNodeVersionFile(
+      fs.readFileSync(versionFilePath, 'utf8')
+    );
+
+    if (parsedVersion) {
+      version = parsedVersion;
+    } else {
+      core.warning(
+        `Could not determine node version from ${versionFilePath}. Falling back`
+      );
+    }
 
     core.info(`Resolved ${versionFileInput} as ${version}`);
   }
diff --git a/src/util.ts b/src/util.ts
index 3ae94a2dc..0b2b14906 100644
--- a/src/util.ts
+++ b/src/util.ts
@@ -1,13 +1,31 @@
 import * as core from '@actions/core';
 import * as exec from '@actions/exec';
 
-export function parseNodeVersionFile(contents: string): string {
+export function parseNodeVersionFile(contents: string): string | null {
   let nodeVersion: string | undefined;
 
   // Try parsing the file as an NPM `package.json` file.
   try {
-    nodeVersion = JSON.parse(contents).volta?.node;
-    if (!nodeVersion) nodeVersion = JSON.parse(contents).engines?.node;
+    const manifest = JSON.parse(contents);
+
+    // JSON can parse numbers, but that's handled later
+    if (typeof manifest === 'object') {
+      nodeVersion = manifest.volta?.node;
+      if (!nodeVersion) nodeVersion = manifest.engines?.node;
+
+      // if contents are an object, we parsed JSON
+      // this can happen if node-version-file is a package.json
+      // yet contains no volta.node or engines.node
+      //
+      // if node-version file is _not_ json, control flow
+      // will not have reached these lines.
+      //
+      // And because we've reached here, we know the contents
+      // *are* JSON, so no further string parsing makes sense.
+      if (!nodeVersion) {
+        return null;
+      }
+    }
   } catch {
     core.info('Node version file is not JSON file');
   }