If you choose the wrong AI, all debugging efforts will be in vain.

Issuing time:2026-04-08 18:00


Choosing the wrong AI can really be a disaster.


I've recently been learning how to install ComfyUI. Although the official website provides a one-click installation package, I've encountered a lot of trouble during the process due to different computer configurations and installation paths.

The world's most popular open-source node-based AI-generated workflow engine


In the past, I might have needed to ask my classmates who write code for help, but now with AI, it's different. I can use AI to learn how to install, how to use, and how to improve efficiency.


But before I started, I chose the wrong AI assistant.


We all know that there are many well-known large models now.

For example, Google's well-known banana model generates images that rank among the best of all current models.


For example, with Volcano's Seedance 2.0, once it appeared, everyone could become a great director;


And then there's Doubao (a type of steamed bun), I bet no working person wouldn't have one installed on their computer.


Today, I highly recommend Claude Sonnet 4.6, the latest release from Anthropic. It is currently the most suitable AI assistant for developers' daily use, bar none.


It solves the common problem that most AIs "can only talk but not do, can only talk about theory but not modify code," and is especially good at troubleshooting complex engineering problems, making it your best partner for writing code, debugging plugins, and setting up environments.


Why do I feel this so deeply?


While learning ComfyUI, I discovered a ComfyUI-Photoshop plugin that allows you to generate and modify images simultaneously within Photoshop, solving the AI ​​gacha problem and improving efficiency.

You can directly edit the output image from AI in Photoshop and optimize details.


I followed the tutorial step by step to install it, but I kept encountering all sorts of problems. Initially, I used Doubao, and I did become dependent on it.


My question is: Why is it that even though the plugin is installed successfully and the raw images run normally on ComfyUI, it still can't transfer the images back to Photoshop?


Doubao gave me many solutions, such as changing nodes, modifying links, and changing code. I did them one by one, but I could only see this ugly frog.

The green frog is the initial interface, indicating that the node is not actually connected.


He even told me to simply drag the images generated in ComfyUI into Photoshop, which would physically solve the problem.


No, then what was the point of me spending so much time and effort installing this plugin?


As you can see, this is the code that Doubao gave me. I'm a complete beginner when it comes to coding, and I can only follow the AI's instructions, but it still can't solve my problem.

from nodes import SaveImage

import hashlib

import asyncio

import json

import base64

import os

import time

import torch

import numpy as np

from PIL import Image, ImageOps

from io import BytesIO

import folder_paths

import torchvision.transforms.functional as tf

import aiohttp

nodepath = os.path.join(

    folder_paths.get_folder_paths("custom_nodes")[0], "comfyui-photoshop"

)

def is_changed_file(filepath):

    try:

        with open(filepath, "rb") as f:

            file_hash = hashlib.md5(f.read()).hexdigest()

        if not hasattr(is_changed_file, "file_hashes"):

            is_changed_file.file_hashes = {}

        if filepath in is_changed_file.file_hashes:

            if is_changed_file.file_hashes[filepath] == file_hash:

                return False

        is_changed_file.file_hashes[filepath] = file_hash

        return float("NaN")

    except Exception as e:

        print(f"Error in is_changed_file for {filepath}: {e}")

        return False

class PhotoshopToComfyUI:

    @classmethod

    def INPUT_TYPES(cls):

        return {"required": {}}

    RETURN_TYPES = ("IMAGE", "MASK", "FLOAT", "INT", "STRING", "STRING", "INT", "INT")

    RETURN_NAMES = ("Canvas", "Mask", "Slider", "Seed", "+", "-", "W", "H")

    FUNCTION = "PS_Execute"

    CATEGORY = "Photoshop"

    def PS_Execute(self):

        self.LoadDir()

        self.loadConfig()

        self.SendImg()

        sliderValue = self.slider / 100

        return (

            self.canvas,

            self.mask.unsqueeze(0),

            sliderValue,

            int(self.seed),

            self.psPrompt,

            self.ngPrompt,

            int(self.width),

            int(self.height),

        )

    def LoadDir(self, retry_count=0):

        try:

            self.canvasDir = os.path.join(

                nodepath, "data", "ps_inputs", "PS_canvas.png"

            )

self.maskImgDir = os.path.join(nodepath, "data", "ps_inputs", "PS_mask.png")

            self.configJson = os.path.join(nodepath, "data", "ps_inputs", "config.json")

        except:

            time.sleep(0.5)

            if retry_count < 4:

                self.LoadDir(retry_count + 1)

            else:

                raise Exception(

                    "Failed to load directory after 5 attempts. \n 🔴 Make sure you have installed and started the Photoshop Plugin Successfully. \n 🔴 otherwise you can restart your Photoshop and your plugin to fix this problem."

                )

    def loadConfig(self, retry_count=0):

        try:

            with open(self.configJson, "r", encoding="utf-8") as file:

                self.ConfigData = json.load(file)

        except:

            time.sleep(0.5)

            if retry_count < 4:

                self.loadConfig(retry_count + 1)

            else:

                raise Exception(

                    "Failed to load config after 5 attempts. \n 🔴 Make sure you have installed and started the Photoshop Plugin Successfully. \n 🔴 otherwise you can restart your Photoshop and your plugin to fix this problem."

                )

        self.psPrompt = self.ConfigData["positive"]

        self.ngPrompt = self.ConfigData["negative"]

        self.seed = self.ConfigData["seed"]

        self.slider = self.ConfigData["slider"]

    def SendImg(self):

        self.loadImg(self.canvasDir)

        self.canvas = self.i.convert("RGB")

        self.canvas = np.array(self.canvas).astype(np.float32) / 255.0

        self.canvas = torch.from_numpy(self.canvas)[None,]

        self.width, self.height = self.i.size

        self.loadImg(self.maskImgDir)

        self.i = ImageOps.exif_transpose(self.i)

        self.mask = np.array(self.i.getchannel("B")).astype(np.float32) / 255.0

        self.mask = torch.from_numpy(self.mask)

        # Convert #010101 to #000000

        self.mask = self.mask.numpy()

        target_color = 1 / 255.0

        self.mask[self.mask == target_color] = 0.0

        self.mask = torch.from_numpy(self.mask)

    def loadImg(self, path):

        try:

            with open(path, "rb") as file:

                img_data = file.read()

            self.i = Image.open(BytesIO(img_data))

            self.i.verify()

            self.i = Image.open(BytesIO(img_data))

        except:

            self.i = Image.new(mode="RGB", size=(24, 24), color=(0, 0, 0))

        if not self.i:

            return

    @classmethod

    def IS_CHANGED(cls):

        try:

            configJson = os.path.join(nodepath, "data", "ps_inputs", "config.json")

            canvasDir = os.path.join(nodepath, "data", "ps_inputs", "PS_canvas.png")

maskImgDir = os.path.join(nodepath, "data", "ps_inputs", "PS_mask.png")

            config_changed = is_changed_file(configJson)

            canvas_changed = is_changed_file(canvasDir)

            mask_changed = is_changed_file(maskImgDir)

            return config_changed or canvas_changed or mask_changed

        except Exception as e:

            print("Error in IS_CHANGED:", e)

            return 0

class ComfyUIToPhotoshop(SaveImage):

    def __init__(self):

        self.output_dir = folder_paths.get_temp_directory()

        self.type = "temp"

        self.prefix_append = "_temp_"

        self.compress_level = 4

    @staticmethod

    def INPUT_TYPES():

        return {

            "required": {

                "output": ("IMAGE",),

            },

            "hidden": {"prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO"},

        }

    FUNCTION = "execute"

    CATEGORY = "Photoshop"

    async def connect_to_backend(self, filename):

        try:

            url = f"http://127.0.0.1:8188/ps/renderdone?filename={filename}"

            async with aiohttp.ClientSession() as session:

                async with session.get(url) as response:

                    return await response.text()

        except Exception as e:

            print(f"_PS_ error on send2Ps: {e}")

    def execute(

        self,

        output: torch.Tensor,

        filename_prefix="PS_OUTPUTS",

        prompt=None,

        extra_pnginfo=None,

    ):

        x = self.save_images(output, filename_prefix, prompt, extra_pnginfo)

        import asyncio

        loop = asyncio.get_event_loop()

        loop.create_task(self.connect_to_backend(x["ui"]["images"][0]["filename"]))

        return x

class ClipPass:

    @classmethod

    def INPUT_TYPES(s):

        return {"required": {"clip": ("CLIP",)}}

    RETURN_TYPES = ("CLIP",)

    RETURN_NAMES = ("clip",)

    FUNCTION = "exe"

    CATEGORY = "utils"

    def exe(self, clip):

        return (clip,)

class modelPass:

    @classmethod

    def INPUT_TYPES(s):

        return {"required": {"model": ("MODEL",)}}

    RETURN_TYPES = ("MODEL",)

    RETURN_NAMES = ("model",)

    FUNCTION = "exe"

    CATEGORY = "utils"

    def exe(self, model):

        return (model,)

NODE_CLASS_MAPPINGS = {

    "🔹Photoshop ComfyUI Plugin": PhotoshopToComfyUI,

    "🔹SendTo Photoshop Plugin": ComfyUIToPhotoshop,

    "🔹ClipPass": ClipPass,

"🔹modelPass": modelPass,

}

NODE_DISPLAY_NAME_MAPPINGS = {

    "PhotoshopToComfyUI": "🔹Photoshop ComfyUI Plugin",

    "SendToPhotoshop": "🔹Send To Photoshop",

    "ClipPass": "🔹ClipPass",

"modelPass": "🔹modelPass",

}


My trust in it lasted for a week, during which time it kept me going in circles. I felt I couldn't let this continue, and that's when I thought of Claude.


Why did I forget about this model? As an operations person, I rarely need to use professional coding models, but it's just so incredibly powerful.


I only asked him two questions:

Could you please help me troubleshoot why I can generate the image in ComfyUI and render it in Photoshop, and the backend does process the image, but it doesn't automatically return to Photoshop? Also, the third image is stuck on an ugly frog that I can't click on. I've attached my UI screenshots and backend code.


He first helped me troubleshoot the problem:

□ 1. Add `--enable-cors-header` to ComfyUI startup

□ 2. The Photoshop plugin's Settings page displays "Connected" (in green).

□ 3. Do not switch Photoshop focus while running (keep the plugin panel visible).

□ 4. SendTo Photoshop node check connection

□ 5. Click "Reload Plugin" and try again.

□ 6. AI Panel ≠ ComfyUI Web; use them separately.


Check my nodePlugin.py file again.

He immediately found my core bug:

def execute(self, output, filename_prefix="PS_OUTPUTS", prompt=None, extra_pnginfo=None):

    x = self.save_images(output, filename_prefix, prompt, extra_pnginfo)

    import asyncio

    loop = asyncio.get_event_loop()

loop.create_task(self.connect_to_backend(x["ui"]["images"][0]["filename"])) # ← The problem is here

    return x



This is the problem analysis process.

loop.create_task()     

↓ 

It simply "registered" an asynchronous task.

↓ 

However, `execute()` is a synchronous function and returns immediately.

↓ 

Asynchronous tasks do not wait for execution to complete.

↓ 

The image was saved, but the request to notify Photoshop was never actually sent.


Then he gave me the complete correct code, which is the code Claude gave me. You can compare it with the one above.

from nodes import SaveImage

import hashlib

import asyncio

import json

import base64

import os

import time

import torch

import numpy as np

from PIL import Image, ImageOps

from io import BytesIO

import folder_paths

import torchvision.transforms.functional as tf

import aiohttp

import urllib.request

import urllib.error

import threading

nodepath = os.path.join(

    folder_paths.get_folder_paths("custom_nodes")[0], "comfyui-photoshop"

)

def is_changed_file(filepath):

    try:

        with open(filepath, "rb") as f:

            file_hash = hashlib.md5(f.read()).hexdigest()

        if not hasattr(is_changed_file, "file_hashes"):

            is_changed_file.file_hashes = {}

        if filepath in is_changed_file.file_hashes:

            if is_changed_file.file_hashes[filepath] == file_hash:

                return False

        is_changed_file.file_hashes[filepath] = file_hash

        return float("NaN")

    except Exception as e:

        print(f"Error in is_changed_file for {filepath}: {e}")

        return False

class PhotoshopToComfyUI:

    @classmethod

    def INPUT_TYPES(cls):

        return {"required": {}}

    RETURN_TYPES = ("IMAGE", "MASK", "FLOAT", "INT", "STRING", "STRING", "INT", "INT")

    RETURN_NAMES = ("Canvas", "Mask", "Slider", "Seed", "+", "-", "W", "H")

    FUNCTION = "PS_Execute"

    CATEGORY = "Photoshop"

    def PS_Execute(self):

        self.LoadDir()

        self.loadConfig()

        self.SendImg()

        sliderValue = self.slider / 100

        return (

            self.canvas,

            self.mask.unsqueeze(0),

            sliderValue,

            int(self.seed),

            self.psPrompt,

            self.ngPrompt,

            int(self.width),

            int(self.height),

        )

    def LoadDir(self, retry_count=0):

        try:

            self.canvasDir = os.path.join(

                nodepath, "data", "ps_inputs", "PS_canvas.png"

            )

self.maskImgDir = os.path.join(nodepath, "data", "ps_inputs", "PS_mask.png")

            self.configJson = os.path.join(nodepath, "data", "ps_inputs", "config.json")

        except:

            time.sleep(0.5)

            if retry_count < 4:

                self.LoadDir(retry_count + 1)

            else:

                raise Exception(

                    "Failed to load directory after 5 attempts. \n 🔴 Make sure you have installed and started the Photoshop Plugin Successfully."

                )

    def loadConfig(self, retry_count=0):

        try:

            with open(self.configJson, "r", encoding="utf-8") as file:

                self.ConfigData = json.load(file)

        except:

            time.sleep(0.5)

            if retry_count < 4:

                self.loadConfig(retry_count + 1)

            else:

                raise Exception(

                    "Failed to load config after 5 attempts. \n 🔴 Make sure you have installed and started the Photoshop Plugin Successfully."

                )

        self.psPrompt = self.ConfigData["positive"]

        self.ngPrompt = self.ConfigData["negative"]

        self.seed = self.ConfigData["seed"]

        self.slider = self.ConfigData["slider"]

    def SendImg(self):

        self.loadImg(self.canvasDir)

        self.canvas = self.i.convert("RGB")

        self.canvas = np.array(self.canvas).astype(np.float32) / 255.0

        self.canvas = torch.from_numpy(self.canvas)[None,]

        self.width, self.height = self.i.size

        self.loadImg(self.maskImgDir)

        self.i = ImageOps.exif_transpose(self.i)

        self.mask = np.array(self.i.getchannel("B")).astype(np.float32) / 255.0

        self.mask = torch.from_numpy(self.mask)

        self.mask = self.mask.numpy()

        target_color = 1 / 255.0

        self.mask[self.mask == target_color] = 0.0

        self.mask = torch.from_numpy(self.mask)

    def loadImg(self, path):

        try:

            with open(path, "rb") as file:

                img_data = file.read()

            self.i = Image.open(BytesIO(img_data))

            self.i.verify()

            self.i = Image.open(BytesIO(img_data))

        except:

            self.i = Image.new(mode="RGB", size=(24, 24), color=(0, 0, 0))

        if not self.i:

            return

    @classmethod

    def IS_CHANGED(cls):

        try:

            configJson = os.path.join(nodepath, "data", "ps_inputs", "config.json")

            canvasDir = os.path.join(nodepath, "data", "ps_inputs", "PS_canvas.png")

maskImgDir = os.path.join(nodepath, "data", "ps_inputs", "PS_mask.png")

            config_changed = is_changed_file(configJson)

            canvas_changed = is_changed_file(canvasDir)

            mask_changed = is_changed_file(maskImgDir)

            return config_changed or canvas_changed or mask_changed

        except Exception as e:

            print("Error in IS_CHANGED:", e)

            return 0

class ComfyUIToPhotoshop(SaveImage):

    def __init__(self):

        self.output_dir = folder_paths.get_temp_directory()

        self.type = "temp"

        self.prefix_append = "_temp_"

        self.compress_level = 4

    @staticmethod

    def INPUT_TYPES():

        return {

            "required": {

                "output": ("IMAGE",),

            },

            "hidden": {"prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO"},

        }

    FUNCTION = "execute"

    CATEGORY = "Photoshop"

    def send_to_ps_sync(self, filename):

        """

Use a separate thread and synchronous requests to notify the PS (Service Packet) to avoid the problem of asynchronous task loss.

With 3 retries, each with a 1-second interval.

        "

""

        url = f"http://127.0.0.1:8188/ps/renderdone?filename={filename}"

       

        for attempt in range(3):

            try:

print(f"_PS_ is sending an image back to Photoshop... {attempt + 1}th attempt")

print(f"_PS_ Request URL: {url}")

               

                req = urllib.request.Request(url)

                with urllib.request.urlopen(req, timeout=15) as response:

                    result = response.read().decode()

print(f"_PS_ ✅ Successful return! Response: {result}")

                    return

                   

            except urllib.error.URLError as e:

print(f"_PS_ ❌ {attempt + 1}th failure (URLError): {e.reason}")

            except Exception as e:

print(f"_PS_ ❌ {attempt + 1}th failure (Exception): {e}")

           

# The last time, no waiting is required

            if attempt < 2:

print(f"_PS_ Retrying in 1 second...")

                time.sleep(1)

       

print("_PS_ ❌ All 3 attempts failed; image could not be sent back to Photoshop")

print("_PS_ Please check: 1. Is the PS plugin connected? 2. Is port 8188 correct?")

    def execute(

        self,

        output: torch.Tensor,

        filename_prefix="PS_OUTPUTS",

        prompt=None,

        extra_pnginfo=None,

    ):

# Save the image first

        x = self.save_images(output, filename_prefix, prompt, extra_pnginfo)

       

        filename = x["ui"]["images"][0]["filename"]

print(f"_PS_ Image saved: {filename}")

       

# Send using a separate thread, without blocking the ComfyUI main thread.

        t = threading.Thread(

            target=self.send_to_ps_sync,

            args=(filename,),

            daemon=True

        )

        t.start()

       

# Wait a maximum of 20 seconds for the return to complete.

        t.join(timeout=20)

       

        if t.is_alive():

print("_PS_ ⚠️ Postback timed out (20s), continuing to attempt in the background...")

       

        return x

class ClipPass:

    @classmethod

    def INPUT_TYPES(s):

        return {"required": {"clip": ("CLIP",)}}

    RETURN_TYPES = ("CLIP",)

    RETURN_NAMES = ("clip",)

    FUNCTION = "exe"

    CATEGORY = "utils"

    def exe(self, clip):

        return (clip,)

class modelPass:

    @classmethod

    def INPUT_TYPES(s):

        return {"required": {"model": ("MODEL",)}}

    RETURN_TYPES = ("MODEL",)

    RETURN_NAMES = ("model",)

    FUNCTION = "exe"

    CATEGORY = "utils"

    def exe(self, model):

        return (model,)

NODE_CLASS_MAPPINGS = {

    "🔹Photoshop ComfyUI Plugin": PhotoshopToComfyUI,

    "🔹SendTo Photoshop Plugin": ComfyUIToPhotoshop,

    "🔹ClipPass": ClipPass,

"🔹modelPass": modelPass,

}

NODE_DISPLAY_NAME_MAPPINGS = {

    "PhotoshopToComfyUI": "🔹Photoshop ComfyUI Plugin",

    "SendToPhotoshop": "🔹Send To Photoshop",

    "ClipPass": "🔹ClipPass",

"modelPass": "🔹modelPass",

}


Problem solved 😭


The whole process took less than 10 minutes.


The saying "everyone has their own expertise" applies not only to humans but also to large-scale AI models.


Doubao is very powerful and can solve many of my daily problems. It can write work summaries and create PPTs for me, but in certain areas, we still need to choose professional large models.


For example, if I want to create a poster or generate an image, I will choose Gemini's model first; if I want to generate music, I will choose Minimax; if I want to write a novel, I will choose GPT; if I need to install software that involves code, I will choose Claude.


Want to know how to quickly access large AI models with just one click?

Welcome to the Nebula API:openai-nebula.com

Everything you want is here⬇️



Nebula Data, headquartered in Singapore, has branches in Jakarta, Guangzhou, Shanghai, and Hong Kong. The company independently developed Nebula Lab, a one-stop AI content generation and model aggregation platform, equipped with an enterprise-grade AI Agent, aggregating globally applicable large-scale models and industry-specific vertical models. Simultaneously, it launched the Nebula AIoT hardware ecosystem (including smart interactive terminals, IoT gateways, and other products), forming a full-link intelligent solution from cloud to edge to device. This provides integrated services to customers in e-commerce, manufacturing, retail, and other fields, from cloud computing power support and AI intelligent decision-making to terminal scenario implementation. Furthermore, it offers global AIDC (AI Intelligent Computing Center) + low-latency network services, empowering enterprises to embrace AI, connect to the physical world, and expand their global business through its technological foundation.