{"record":{"id":"2f289075c98c9868","repo":"different-ai/openwork","slug":"cannot-save-non-file-artifact","errorCode":null,"errorMessage":"Cannot save non-file artifact.","messagePattern":"Cannot save non-file artifact\\.","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"apps/app/src/react-app/domains/session/artifacts/artifact-panel.tsx","lineNumber":184,"sourceCode":"\n  useEffect(() => {\n    setEditing(false);\n    setDraft(\"\");\n    lastSyncedRef.current = null;\n    failedDraftRef.current = null;\n  }, [target.id, workspaceId]);\n\n  useEffect(() => {\n    if (data?.kind === \"text\" && data.data !== lastSyncedRef.current) {\n      lastSyncedRef.current = data.data;\n      setDraft(data.data);\n    }\n  }, [data]);\n\n  const { mutate, mutateAsync, isPending: isSaving } = useMutation({\n    mutationFn: async (input: SaveArtifactInput) => {\n      if (target.kind !== \"file\") {\n        throw new Error(\"Cannot save non-file artifact.\");\n      }\n\n      if (input.kind === \"text\") {\n        return client.writeWorkspaceFile(workspaceId, { path: target.value, content: input.data, baseUpdatedAt: input.baseUpdatedAt });\n      }\n\n      return client.writeWorkspaceBinaryFile(workspaceId, { path: target.value, data: input.data, baseUpdatedAt: input.baseUpdatedAt });\n    },\n    onSuccess: (result, input) => {\n      queryClient.setQueryData<ArtifactQueryState>(\n        [\"artifact-panel\", workspaceId, target.id, target.updatedAt ?? null] as const,\n        input.kind === \"text\"\n          ? { kind: \"text\", data: input.data, updatedAt: result.updatedAt ?? null }\n          : { kind: \"binary\", data: input.data, contentType: data?.kind === \"binary\" ? data.contentType : null, updatedAt: result.updatedAt ?? null },\n      );\n\n      if (input.kind === \"text\") {\n        lastSyncedRef.current = input.data;","sourceCodeStart":166,"sourceCodeEnd":202,"githubUrl":"https://github.com/different-ai/openwork/blob/2b7df46e8ae1517d64c896c7793d2d52ec845669/apps/app/src/react-app/domains/session/artifacts/artifact-panel.tsx#L166-L202","documentation":"The artifact save mutation in ArtifactPanelView only supports artifacts whose target is a file (path-backed). If the selected artifact's target.kind is not 'file' (e.g. a terminal/buffer or other in-memory target), there is no workspace file path to write to, so the mutation throws before calling client.writeWorkspaceFile.","triggerScenarios":"Clicking Save in the artifact panel while the panel's `target.kind` is anything other than \"file\"; calling the mutation with a SaveArtifactInput whose underlying target is a non-file artifact (e.g. text captured from a tool output rather than a workspace file).","commonSituations":"Users open an artifact derived from a chat/tool response or a virtual buffer and hit Save expecting it to persist; a target computed from stale or wrong panel state; UI allowing Save on artifact types it cannot persist.","solutions":["Only render/enable the Save button when target.kind === \"file\"","Before mutating, check target.kind and route non-file targets to a different save path or show a disabled state","If the artifact should be file-backed, ensure the target is resolved to a workspace file path before opening the panel","Catch the error in an onError handler and surface a clear 'this artifact type cannot be saved' message"],"exampleFix":"// before\nawait mutateAsync({ kind: \"text\", data, baseUpdatedAt });\n// after\nif (target.kind !== \"file\") {\n  toast.error(\"Only file artifacts can be saved to the workspace.\");\n  return;\n}\nawait mutateAsync({ kind: \"text\", data, baseUpdatedAt });","handlingStrategy":"validation","validationCode":"if (target.kind !== \"file\") {\n  toast.error(\"Only file artifacts can be saved to the workspace.\");\n  return;\n}","typeGuard":"const isFileTarget = (t: Target): t is Extract<Target, { kind: \"file\" }> => t.kind === \"file\";","tryCatchPattern":"try {\n  await mutateAsync(input);\n} catch (e) {\n  if (e instanceof Error && e.message === \"Cannot save non-file artifact.\") {\n    toast.error(\"This artifact type cannot be saved as a file.\");\n  } else throw e;\n}","preventionTips":["Gate the Save button on target.kind === \"file\"","Type-narrow target with a discriminated union before the mutation","Resolve artifacts to file-backed targets when opening the panel","Add an onError handler on the mutation to surface friendly messages"],"tags":["ui","guard-clause","artifacts"],"backgroundTag":"unsupported-artifact-save","analyzedSha":"2b7df46e8ae1517d64c896c7793d2d52ec845669","analyzedAt":"2026-09-01T07:59:23.713Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}