diff --git a/internal/api/docker_container_action_executor.go b/internal/api/docker_container_action_executor.go index 133150a91..9c3a76b8d 100644 --- a/internal/api/docker_container_action_executor.go +++ b/internal/api/docker_container_action_executor.go @@ -326,7 +326,7 @@ func (e dockerContainerActionExecutor) CheckActionAvailable(ctx context.Context, agentID, _ := e.connectedDockerCommandAgentID(resource) liveCapability, supported := e.agents.(agentOperationReceiptCapability) if !supported || liveCapability.AgentOperationReceiptVersion(agentID) != operationreceipt.ProtocolVersion { - return unavailableDockerActionReadiness(operation, "operation_receipt_unsupported", "The Pulse agent on this host is still on an older version that cannot run reviewed actions. Wait for the agent to update itself or update it manually, then retry.") + return unavailableDockerActionReadiness(operation, "operation_receipt_unsupported", "The Pulse agent on this host cannot run reviewed actions: it is on an older version, or its durable state directory is unavailable. Update the agent, or check the agent logs if it is already current, then retry.") } return readiness } diff --git a/internal/api/host_storage_cleanup_action_executor.go b/internal/api/host_storage_cleanup_action_executor.go index 33c608c1a..797316467 100644 --- a/internal/api/host_storage_cleanup_action_executor.go +++ b/internal/api/host_storage_cleanup_action_executor.go @@ -265,7 +265,7 @@ func hostStorageCleanupUnavailableReason(err error) string { case "host_commands_disabled": return "Typed host operations are disabled for this agent." case "operation_receipt_unsupported": - return "The Pulse agent on this host is still on an older version that cannot run reviewed actions. Wait for the agent to update itself or update it manually, then retry." + return "The Pulse agent on this host cannot run reviewed actions: it is on an older version, or its durable state directory is unavailable. Update the agent, or check the agent logs if it is already current, then retry." case "unsupported_cleanup_provider": return "This host does not expose a supported package-cache cleanup provider." case "cleanup_inventory_error": diff --git a/internal/api/host_update_action_executor.go b/internal/api/host_update_action_executor.go index 8a057a88d..32f12e9b9 100644 --- a/internal/api/host_update_action_executor.go +++ b/internal/api/host_update_action_executor.go @@ -272,7 +272,7 @@ func hostUpdateUnavailableReason(err error) string { case "host_commands_disabled": return "Typed host operations are disabled for this agent." case "operation_receipt_unsupported": - return "The Pulse agent on this host is still on an older version that cannot run reviewed actions. Wait for the agent to update itself or update it manually, then retry." + return "The Pulse agent on this host cannot run reviewed actions: it is on an older version, or its durable state directory is unavailable. Update the agent, or check the agent logs if it is already current, then retry." case "unsupported_package_manager": return "This host does not expose a supported package manager." case "package_inventory_error": diff --git a/internal/hostagent/commands.go b/internal/hostagent/commands.go index fea5756a8..96129a520 100644 --- a/internal/hostagent/commands.go +++ b/internal/hostagent/commands.go @@ -132,6 +132,14 @@ func NewCommandClient(cfg Config, agentID, hostname, platform, version string) * } receipts, receiptErr := operationreceipt.Open(filepath.Join(stateDir, "operation-receipts.db"), hostOperationReceiptConfig()) + if receiptErr != nil { + // Without the durable receipt store the agent registers with operation + // receipt version 0 and Pulse refuses every reviewed action for this + // host. Surface the real cause loudly; otherwise the only symptom is a + // misleading "agent is too old" readiness message on the server. + logger.Warn().Err(receiptErr).Str("state_dir", stateDir). + Msg("Operation receipt store unavailable — Pulse will refuse reviewed actions for this agent until its state directory is writable") + } return &CommandClient{ pulseURL: strings.TrimRight(cfg.PulseURL, "/"), apiToken: cfg.APIToken,