Error: host is in a modal state

with 24.1.1 release some of our plugin users are getting that error now: “host is in a modal state”.
This is unchanged code for months. And after really doing a lot of debugging we came to the conclusion that there is no easy fix here:

  • it comes very random: never, sometimes or always at different position of code (very different executeAsModal calls) which makes it impossible to find a solution here
  • we suspect that this comes only on users who have got many plugins installed
    So is it technically possible that two plugins could have a conflict each other? like if one plugin is requesting modal state in background in interval (e.g. some kind of batch operations) so another request with executeAsModal will fail? What is the best solution to get executeAsModal working here?
    Thank you!

We got some more insight here after testing and this is very important for all plugin developers in my opinion.

  1. it already mentioned in documentation: “Only one plugin can be modal at any given time. If another plugin is modal when you call executeAsModal, then executeAsModal will raise an error. It is therefore important to handle errors when calling this method.”
  2. we already had a very similar error handling as in that example there:
    if (e.number == 9) {
    showAlert(“executeAsModal was rejected (some other plugin is currently inside a modal scope)”);

This is the problem here! It is just new that a plugin can constantly requesting a modal state. And this is happing out in the wild. The problem here is that such an error handling like above will break the plugin in such a situation for 100%. Meaning all plugins would break now on such installations. We do not know if this problem is connected with 24.1.1 or just a crazy not good implemented plugin is doing such things here. More research has do be done here. It could be also a plugin not from CC.

This is the only workaround you have and in our opinion not getting in trouble:

   async executeAsModal(actionCommands,paramObject={ "commandName": "Action Commands" }) {
        let result;
        let failed = true;
        while(failed) {
            try {
                result = await core.executeAsModal(actionCommands, paramObject);
                failed = false;
            } catch (e) {
                if (e.number == 9) {
// just do nothing here - this is important, not showing an error
                } else {
                    failed = false;
                    // This case is hit if the targetFunction throws an exception
            await new Promise(r => setTimeout(r, 200));
        return result;

Few tips:

  1. wrap all your steps that you don’t want to be interrupted into single modal execution
  2. could check for modal state like this?
  3. could you hook execution to the next “idle” event? Or maybe better “modalStateChanged” with value “exit”?

thank you for reply:

  1. this is not practical in a very large application and does not solve the problem
  2. also does not solve the problem
  3. hmm not sure about this but I provided a solution already above and to be honest this should be part of the documentation.
    The source of the problem is that everybody was thinking that a plugin works like this:
  • the user presses on a button in one plugin
  • that plugin is doing something with executeAsModal
  • the user presses on a button in another plugin
  • that other plugin is doing something with executeAsModal
    Our plugin behaviour is also like this and so was the expectation of implementing that function and writing the documentation. Meaning: it only works if everybody is nice to each other.

But if a strange plugin (maybe not from CC) is doing something bad and requesting several times in a second without stopping executeAsModal it will break all other plugins. And this is happening for 1-2 weeks now and we have not seen it before.

You could try disabling installed plugins one by one to see if it’s one of those causing problem. You would at least narrow down the cause

yes I am trying to get that information

Well e.g. in my Mask Checker plugin I do set metadata everytime when document is changed. This can’t be done outside of modal execution and also you cannot really delay data too much because when user closes a document you can’t really intercept that and in such a case metadata would be out of sync.

So that could be one example.

My tips are more workarounds like yours when you wait a bit to try later. But with some fine tuning you could time it exactly at point when modal state exits.

excuse my non-contribution to the exact question.

it’s more of a related suggestion for the devs

it would be nice if we can “queue” tasks to be executed after the current modal state is done.

maybe a Boolean to signify that we don’t mind waiting for whatever holding the modal state to exit then our task is automatically executed right after that.

Well this is implemented for batchPlay modalBehavior: "wait" and other options fail and execute. For DOM API this is always set to execute.

I agree there should be some improvement in this behavior.

1 Like

I had this problem too and the customer and I traced it to another plugin, not mine. I had the customer uninstall all his plugins and then reinstall them one at a time until the problem showed up again, and it was obvious which panel was causing it. We further found that simply closing the offending plugin panel to its docking icon was enough keep it from constantly throwing modal states. Needless to say, that plugin is probably not getting used much anymore.

While it’s necessary to use executeAsModal() when making changes to documents in Photoshop, it is not necessary to use it for all batchPlay, like with getters. I know the offending plugin has a getter that runs in the background and assume it was encased in executeAsModal() that probably fires multiple times/sec and will likely interfere with other plugins as well. Feel free to message me if you want more info.

I guess this is a side effect (or rather the downside) of allowing plugins to subscribe/listen to all sorts of events and then do whatever they want in the corresponding callback. It seems like it would be useful if Photoshop could detect this behavior with some metric and flag an offending plugin in one way or the other. Some sort of plugin monitor might be useful. :thinking:

it’s surly a downside when it gets abused or integrated in a badly designed flow. but this can be dealt with by implementing a queue of tasks, showing the user which plugins are running in the background (a graph, meter or a counter) to help quality control plugins and keep them in check.

some SPECIAL use cases do need recurring tasks but it’s a very bad design choice when not clearly communicated to the user.

1 Like

I should be clear that when I encountered this problem with another UXP panel, it was in the following situation where a button click called three separate functions that were NOT enclosed in executeAsModal() as they were called:

document.getElementById("myButton").addEventListener("click", async () => {
    await myFunction1();
    await myFunction2();
    await myFunction3();

The functions WERE enclosed in executeAsModal() when they executed, but not when they were called. Once I enclosed these three functions in executeAsModal() when they were called, the other plugin was unable to cause problems in this particular situation, which, of course, makes sense.

I think something like this would be a good idea.

I’ve just started looking at UXP plugin development and wanted to implement something from our old CEP extension - making a specific layer visible automatically when a document is opened.

So I added a notification listener for the open event and connected it to function that finds the layer with a specific name and makes it visible.
But making a layer visible must be done in a modal scope.
So I call the function with executeAsModal().
But this fails with error number 9 - I assume because Photoshop fires the open event while it’s still in a modal scope for opening the document.
The solution I came up with is similar to what @Imperator showed above - retrying until it works.

So this is not just a problem with plugins misbehaving or multiple plugins tripping over each other.
It can happen with just a single plugin.