diff --git a/src/PSProxmoxVE.Core/Utilities/GuestStatusSnapshot.cs b/src/PSProxmoxVE.Core/Utilities/GuestStatusSnapshot.cs
new file mode 100644
index 0000000..457764a
--- /dev/null
+++ b/src/PSProxmoxVE.Core/Utilities/GuestStatusSnapshot.cs
@@ -0,0 +1,40 @@
+using System;
+using Newtonsoft.Json.Linq;
+
+namespace PSProxmoxVE.Core.Utilities
+{
+ ///
+ /// Reads the fields of a guest status/current response that decide whether a
+ /// lifecycle wait (-Wait on Start, Stop, Restart, Reset, Resume) is finished.
+ ///
+ public static class GuestStatusSnapshot
+ {
+ ///
+ /// Evaluates a status/current response body against the status a caller is waiting for.
+ ///
+ /// Raw status/current response body.
+ /// The status being waited for (e.g. "running", "stopped", "paused").
+ ///
+ /// StatusMatched: the guest reports . qmpstatus is
+ /// preferred over status when present, because PVE reports status=running with
+ /// qmpstatus=paused for a suspended VM.
+ /// Locked: the guest config still carries a lock, so the next API call against it
+ /// would fail to acquire the lock file.
+ ///
+ public static (bool StatusMatched, bool Locked) Evaluate(string json, string expectedStatus)
+ {
+ if (string.IsNullOrEmpty(json))
+ return (false, false);
+
+ var data = JObject.Parse(json)["data"];
+ var status = data?["status"]?.ToString();
+ var qmpStatus = data?["qmpstatus"]?.ToString();
+ var effectiveStatus = qmpStatus ?? status;
+
+ var matched = string.Equals(effectiveStatus, expectedStatus, StringComparison.OrdinalIgnoreCase);
+ var locked = !string.IsNullOrEmpty(data?["lock"]?.ToString());
+
+ return (matched, locked);
+ }
+ }
+}
diff --git a/tests/PSProxmoxVE.Core.Tests/Utilities/GuestStatusSnapshotTests.cs b/tests/PSProxmoxVE.Core.Tests/Utilities/GuestStatusSnapshotTests.cs
new file mode 100644
index 0000000..bb47d53
--- /dev/null
+++ b/tests/PSProxmoxVE.Core.Tests/Utilities/GuestStatusSnapshotTests.cs
@@ -0,0 +1,70 @@
+using PSProxmoxVE.Core.Utilities;
+using Xunit;
+
+namespace PSProxmoxVE.Core.Tests.Utilities
+{
+ public class GuestStatusSnapshotTests
+ {
+ [Fact]
+ public void Evaluate_RunningAndUnlocked_MatchesAndIsNotLocked()
+ {
+ var json = @"{""data"": {""status"": ""running"", ""qmpstatus"": ""running""}}";
+
+ var result = GuestStatusSnapshot.Evaluate(json, "running");
+
+ Assert.True(result.StatusMatched);
+ Assert.False(result.Locked);
+ }
+
+ [Fact]
+ public void Evaluate_RunningButStillLocked_MatchesAndIsLocked()
+ {
+ var json = @"{""data"": {""status"": ""running"", ""qmpstatus"": ""running"", ""lock"": ""clone""}}";
+
+ var result = GuestStatusSnapshot.Evaluate(json, "running");
+
+ Assert.True(result.StatusMatched);
+ Assert.True(result.Locked);
+ }
+
+ [Fact]
+ public void Evaluate_PrefersQmpStatusOverStatus()
+ {
+ // PVE reports status=running with qmpstatus=paused for a suspended VM.
+ var json = @"{""data"": {""status"": ""running"", ""qmpstatus"": ""paused""}}";
+
+ Assert.False(GuestStatusSnapshot.Evaluate(json, "running").StatusMatched);
+ Assert.True(GuestStatusSnapshot.Evaluate(json, "paused").StatusMatched);
+ }
+
+ [Fact]
+ public void Evaluate_ContainerResponseWithoutQmpStatus_FallsBackToStatus()
+ {
+ var json = @"{""data"": {""status"": ""stopped""}}";
+
+ var result = GuestStatusSnapshot.Evaluate(json, "stopped");
+
+ Assert.True(result.StatusMatched);
+ Assert.False(result.Locked);
+ }
+
+ [Fact]
+ public void Evaluate_EmptyLockValue_IsNotLocked()
+ {
+ var json = @"{""data"": {""status"": ""stopped"", ""lock"": """"}}";
+
+ Assert.False(GuestStatusSnapshot.Evaluate(json, "stopped").Locked);
+ }
+
+ [Theory]
+ [InlineData("")]
+ [InlineData(null)]
+ public void Evaluate_EmptyBody_DoesNotMatch(string? json)
+ {
+ var result = GuestStatusSnapshot.Evaluate(json!, "running");
+
+ Assert.False(result.StatusMatched);
+ Assert.False(result.Locked);
+ }
+ }
+}