diff --git a/apps/pi/pi/services/retrievers/pg_store/chat.py b/apps/pi/pi/services/retrievers/pg_store/chat.py
index 4a3ebd30cd..270f67351f 100644
--- a/apps/pi/pi/services/retrievers/pg_store/chat.py
+++ b/apps/pi/pi/services/retrievers/pg_store/chat.py
@@ -325,7 +325,23 @@ async def extract_execution_status_from_flow_steps(
if is_executed and is_successful:
action_data["message"] = "Action completed successfully"
elif is_executed and not is_successful:
- action_data["error"] = "Action failed"
+ # Prefer the detailed execution_error from the flow step, then fall back to execution_result
+ error_message = None
+ try:
+ if getattr(step, "execution_error", None):
+ error_message = str(step.execution_error)
+ elif step.execution_data and isinstance(step.execution_data, dict):
+ exec_result = step.execution_data.get("execution_result")
+ if exec_result:
+ error_message = str(exec_result)
+ except Exception:
+ error_message = None
+
+ # Truncate very long errors to keep the response compact
+ if error_message:
+ action_data["error"] = f"{error_message[:200]}..." if len(error_message) > 200 else error_message
+ else:
+ action_data["error"] = None
actions.append(action_data)
diff --git a/apps/web/ee/components/pi-chat/actions/action-status-block.tsx b/apps/web/ee/components/pi-chat/actions/action-status-block.tsx
index 3116144bf3..a629ebaf92 100644
--- a/apps/web/ee/components/pi-chat/actions/action-status-block.tsx
+++ b/apps/web/ee/components/pi-chat/actions/action-status-block.tsx
@@ -21,8 +21,9 @@ type TProps = {
const ActionStatusBlock = (props: TProps) => {
// props
+
const { isLatest, isPiThinking, workspaceSlug, workspaceId, query_id, activeChatId, isPiTyping, dialogue } = props;
- const { execution_status, action_summary, actions } = dialogue;
+ const { execution_status, action_summary, actions, action_error } = dialogue;
// states
const [isExecutingAction, setIsExecutingAction] = useState(false);
// store
@@ -43,7 +44,7 @@ const ActionStatusBlock = (props: TProps) => {
setToast({
type: TOAST_TYPE.ERROR,
title: "Action failed!",
- message: e?.detail,
+ message: e?.error ?? e?.detail ?? e?.message ?? "Unable to execute action.",
});
} finally {
setIsExecutingAction(false);
@@ -78,15 +79,24 @@ const ActionStatusBlock = (props: TProps) => {
);
// Render summary if execution status is executing or action summary is present
+ const shouldShowSummary = execution_status === EExecutionStatus.EXECUTING || Boolean(action_summary);
+
+ if (!shouldShowSummary && !action_error) return null;
+
return (
- (execution_status === EExecutionStatus.EXECUTING || action_summary) && (
-
- )
+
+ {action_error && (
+
{action_error}
+ )}
+ {shouldShowSummary && (
+
+ )}
+
);
};
diff --git a/apps/web/ee/components/pi-chat/actions/summary.tsx b/apps/web/ee/components/pi-chat/actions/summary.tsx
index 21cd189283..e309748fdf 100644
--- a/apps/web/ee/components/pi-chat/actions/summary.tsx
+++ b/apps/web/ee/components/pi-chat/actions/summary.tsx
@@ -109,11 +109,23 @@ export const SummaryBlock = observer((props: TProps) => {
{groupedArtifacts.failed.map((artifact) => (
-
-
{getIcon(artifact.artifact_type, "", "text")}
-
- {artifact.entity_name || "Unknown"}
-
+
+ {(artifact.entity_name || artifact.action) && (
+
+
{getIcon(artifact.artifact_type, "", "text")}
+
+ {artifact.entity_name || artifact.action}
+
+
+ )}
+ {artifact.error && (
+
+ {artifact.error}
+
+ )}
+ {!artifact.error && artifact.message && (
+
{artifact.message}
+ )}
))}
diff --git a/apps/web/ee/services/pi-chat.service.ts b/apps/web/ee/services/pi-chat.service.ts
index 79f4e6e75e..7518c1c692 100644
--- a/apps/web/ee/services/pi-chat.service.ts
+++ b/apps/web/ee/services/pi-chat.service.ts
@@ -282,7 +282,11 @@ export class PiChatService extends APIService {
return this.post(`/api/v1/chat/execute-action/`, data)
.then((response) => response?.data)
.catch((error) => {
- throw error?.response?.data;
+ throw (
+ error?.response?.data ?? {
+ error: error?.message ?? "Unable to execute action",
+ }
+ );
});
}
diff --git a/apps/web/ee/store/pi-chat/pi-chat.ts b/apps/web/ee/store/pi-chat/pi-chat.ts
index 7e03e587e9..73c16f458b 100644
--- a/apps/web/ee/store/pi-chat/pi-chat.ts
+++ b/apps/web/ee/store/pi-chat/pi-chat.ts
@@ -256,10 +256,25 @@ export class PiChatStore implements IPiChatStore {
dialogue.actions.forEach((action) => {
const artifact = this.artifactsStore.getArtifact(action.artifact_id);
if (!artifact) return;
+
+ // Pull in error/message/entity info from the action as a backup.
+ // This way, users still see why an action failed even after a page refresh.
+ const mergedArtifact: TArtifact = {
+ ...artifact,
+ action: artifact.action || action.action,
+ artifact_type: artifact.artifact_type || action.artifact_type,
+ entity_name: artifact.entity_name || action.entity?.entity_name,
+ entity_url: artifact.entity_url || action.entity?.entity_url,
+ // If we have live artifact error/message, use that; otherwise, fall back
+ // to the error/message that came back with the chat history.
+ error: artifact.error ?? action.error,
+ message: artifact.message ?? action.message,
+ };
+
if (action.success) {
- response.successful.push(artifact);
+ response.successful.push(mergedArtifact);
} else {
- response.failed.push(artifact);
+ response.failed.push(mergedArtifact);
}
});
return response;
@@ -913,6 +928,8 @@ export class PiChatStore implements IPiChatStore {
issue_identifier: action.entity?.issue_identifier,
is_executed: true,
success: action.success,
+ error: action.error,
+ message: action.message,
});
if (action.success) {
actionableEntities.push(action.artifact_type);
@@ -920,6 +937,7 @@ export class PiChatStore implements IPiChatStore {
});
// update dialogue
dialogue.execution_status = EExecutionStatus.COMPLETED;
+ dialogue.action_error = undefined;
dialogue.action_summary = response.action_summary;
dialogue.actions = response.actions;
this.updateDialogue(chatId, actionId, dialogue);
@@ -928,6 +946,9 @@ export class PiChatStore implements IPiChatStore {
} catch (error) {
console.error(error);
dialogue.execution_status = EExecutionStatus.COMPLETED;
+ dialogue.action_error =
+ (error as any)?.error ?? (error as any)?.detail ?? (error as any)?.message ?? "Unable to execute action.";
+ dialogue.action_summary = undefined;
this.updateDialogue(chatId, actionId, dialogue);
throw error;
}
diff --git a/apps/web/ee/types/pi-chat.d.ts b/apps/web/ee/types/pi-chat.d.ts
index 91495604bf..40f077f407 100644
--- a/apps/web/ee/types/pi-chat.d.ts
+++ b/apps/web/ee/types/pi-chat.d.ts
@@ -87,6 +87,8 @@ export type TArtifact = {
is_editable: boolean;
is_executed: boolean;
success: boolean;
+ error?: string;
+ message?: string;
artifact_type: string;
entity_id?: string;
entity_url?: string;
@@ -120,6 +122,15 @@ export type TArtifact = {
success?: boolean;
};
+export type TArtifactWithEntity = TArtifact & {
+ entity?: {
+ entity_id: string;
+ entity_url: string;
+ issue_identifier: string;
+ entity_name: string;
+ };
+};
+
export type TDialogue = {
query_id?: string;
answer_id?: string;
@@ -130,7 +141,7 @@ export type TDialogue = {
reasoning?: string;
isPiThinking: boolean;
execution_status?: EExecutionStatus;
- actions?: TArtifact[];
+ actions?: TArtifactWithEntity[];
action_summary?: {
completed: number;
duration_seconds: number;
@@ -138,6 +149,7 @@ export type TDialogue = {
total_planned: number;
};
attachment_ids?: string[];
+ action_error?: string;
};
export type TChatHistory = {
@@ -167,9 +179,7 @@ export type TAction = {
export type TExecuteActionResponse = {
status: string;
message: string;
- actions: Array<
- TArtifact & { entity?: { entity_id: string; entity_url: string; issue_identifier: string; entity_name: string } }
- >;
+ actions: TArtifactWithEntity[];
action_summary?: {
completed: number;
duration_seconds: number;