From 8255e506a61b5b88dc2774676babdf598fda0f6b Mon Sep 17 00:00:00 2001
From: "goodolclint-claude[bot]"
<323206664+goodolclint-claude[bot]@users.noreply.github.com>
Date: Wed, 2 Sep 2026 17:32:09 +0000
Subject: [PATCH] fix: recognize boolean and varied integer formats for guest
exec exited status (#160)
* fix: recognize boolean and varied integer formats for guest exec exited status
The guest-agent schema declares 'exited' as a boolean, but the PVE API
may return it in different formats: as a JSON boolean true, or as integers
1/0, or string '1'/'0'. The cmdlet was only checking for long 1L, causing
it to timeout on any PVE build that passed a boolean true unchanged.
Add ApiValueHelper.IsExited() to normalize these values and recognize
true, 1L, 1, and '1' as exited. Update the polling loop to use it.
* test: add integration tests for ApiValueHelper with real JSON payloads
Add tests that feed JSON data through the actual JsonHelper.ToNative
parsing pipeline to verify ApiValueHelper.IsExited correctly recognizes
boolean true and numeric 1 values as they arrive from the PVE API.
These tests pin the contract between the JSON parsing layer and the
value-recognition logic, ensuring the fix for issue #141 works end-to-end
with real API response shapes.
* fix: restore correct IsExited implementation with type checks
The helper must handle boolean true, long/int 1, and string "1" as the
issue specifies. This restores the correct multi-type check that was
inadvertently reverted.
* fix: correct boolean value handling in IsExited
---------
Co-authored-by: goodolclint-claude[bot] <323206664+goodolclint-claude[bot]@users.noreply.github.com>
---
.../Utilities/ApiValueHelper.cs | 38 ++++++++++++++
.../Cmdlets/Vms/InvokePveVmGuestExecCmdlet.cs | 3 +-
.../Utilities/ApiValueHelperTests.cs | 52 +++++++++++++++++++
3 files changed, 92 insertions(+), 1 deletion(-)
create mode 100644 src/PSProxmoxVE.Core/Utilities/ApiValueHelper.cs
create mode 100644 tests/PSProxmoxVE.Core.Tests/Utilities/ApiValueHelperTests.cs
diff --git a/src/PSProxmoxVE.Core/Utilities/ApiValueHelper.cs b/src/PSProxmoxVE.Core/Utilities/ApiValueHelper.cs
new file mode 100644
index 0000000..8296cae
--- /dev/null
+++ b/src/PSProxmoxVE.Core/Utilities/ApiValueHelper.cs
@@ -0,0 +1,38 @@
+using System;
+
+namespace PSProxmoxVE.Core.Utilities
+{
+ ///
+ /// Helper methods for normalizing and checking values received from the Proxmox VE API.
+ /// The API may return the same logical value in different formats (e.g., boolean true, integer 1, or string "1").
+ ///
+ public static class ApiValueHelper
+ {
+ ///
+ /// Determines if a value represents a true/exited state.
+ /// Accepts boolean true, integer 1 (as Int64 or Int32), and string "1" as true.
+ /// All other values (false, 0, "0", null, etc.) are false.
+ ///
+ /// The value to check, typically from API response data.
+ /// True if the value represents an exited/true state, false otherwise.
+ public static bool IsExited(object? value)
+ {
+ if (value == null)
+ return false;
+
+ if (value is bool b)
+ return b;
+
+ if (value is long l)
+ return l == 1L;
+
+ if (value is int i)
+ return i == 1;
+
+ if (value is string s)
+ return s == "1";
+
+ return false;
+ }
+ }
+}
diff --git a/src/PSProxmoxVE/Cmdlets/Vms/InvokePveVmGuestExecCmdlet.cs b/src/PSProxmoxVE/Cmdlets/Vms/InvokePveVmGuestExecCmdlet.cs
index b61ba1e..1c1d523 100644
--- a/src/PSProxmoxVE/Cmdlets/Vms/InvokePveVmGuestExecCmdlet.cs
+++ b/src/PSProxmoxVE/Cmdlets/Vms/InvokePveVmGuestExecCmdlet.cs
@@ -2,6 +2,7 @@ using System;
using System.Diagnostics;
using System.Management.Automation;
using PSProxmoxVE.Core.Services;
+using PSProxmoxVE.Core.Utilities;
namespace PSProxmoxVE.Cmdlets.Vms
{
@@ -60,7 +61,7 @@ namespace PSProxmoxVE.Cmdlets.Vms
if (sw.Elapsed >= deadline)
throw new TimeoutException($"Guest command did not complete within {Timeout} seconds.");
result = service.GetGuestExecStatus(session, Node, VmId, pid);
- } while (!result.TryGetValue("exited", out var exited) || !Equals(exited, 1L));
+ } while (!result.TryGetValue("exited", out var exited) || !ApiValueHelper.IsExited(exited));
var output = new PSObject();
output.Properties.Add(new PSNoteProperty("ExitCode", result.TryGetValue("exitcode", out var ec) && ec is long ecl ? (int)ecl : -1));
diff --git a/tests/PSProxmoxVE.Core.Tests/Utilities/ApiValueHelperTests.cs b/tests/PSProxmoxVE.Core.Tests/Utilities/ApiValueHelperTests.cs
new file mode 100644
index 0000000..1d69253
--- /dev/null
+++ b/tests/PSProxmoxVE.Core.Tests/Utilities/ApiValueHelperTests.cs
@@ -0,0 +1,52 @@
+using Newtonsoft.Json.Linq;
+using PSProxmoxVE.Core.Utilities;
+using Xunit;
+
+namespace PSProxmoxVE.Core.Tests.Utilities
+{
+ public class ApiValueHelperTests
+ {
+ [Theory]
+ [InlineData(true)]
+ [InlineData(1L)]
+ [InlineData(1)]
+ [InlineData("1")]
+ public void IsExited_TrueValues_ReturnsTrue(object value)
+ {
+ Assert.True(ApiValueHelper.IsExited(value));
+ }
+
+ [Theory]
+ [InlineData(false)]
+ [InlineData(0L)]
+ [InlineData(0)]
+ [InlineData("0")]
+ [InlineData(null)]
+ [InlineData("")]
+ [InlineData("true")]
+ [InlineData(2L)]
+ [InlineData(42)]
+ public void IsExited_FalseValues_ReturnsFalse(object? value)
+ {
+ Assert.False(ApiValueHelper.IsExited(value));
+ }
+
+ [Theory]
+ [InlineData("{\"data\":{\"exited\":true}}", true)]
+ [InlineData("{\"data\":{\"exited\":false}}", false)]
+ [InlineData("{\"data\":{\"exited\":1}}", true)]
+ [InlineData("{\"data\":{\"exited\":0}}", false)]
+ [InlineData("{\"data\":{\"exited\":\"1\"}}", true)]
+ [InlineData("{\"data\":{\"exited\":\"0\"}}", false)]
+ [InlineData("{\"data\":{\"exited\":null}}", false)]
+ [InlineData("{\"data\":{}}", false)]
+ public void IsExited_ApiJsonValues_ReturnExpectedCompletionState(string json, bool expected)
+ {
+ var data = (JObject)JObject.Parse(json)["data"]!;
+ var status = JsonHelper.ToDictionary(data);
+ var completed = status.TryGetValue("exited", out var exited) && ApiValueHelper.IsExited(exited);
+
+ Assert.Equal(expected, completed);
+ }
+ }
+}