Forum
    • Categories
    • Register
    • Login

    Claude screenshot crashes Hise

    Scheduled Pinned Locked Moved Bug Reports
    5 Posts 3 Posters 70 Views
    Loading More Posts
    • Oldest to Newest
    • Newest to Oldest
    • Most Votes
    Reply
    • Reply as topic
    Log in to reply
    This topic has been deleted. Only users with topic management privileges can see it.
    • ustkU
      ustk
      last edited by ustk

      @Christoph-Hart Seems to happen a lot more since the last commits

      CLAUDE:

      HISE 4.9.3 (102de5c4) — recurrent SIGABRT via the REST API screenshot endpoints

      Symptom. HISE dies with Abort trap: 6.
      8 of the 33 crash reports on this machine (2026‑08‑19 → 2026‑08‑23) have byte‑identical stacks:

      std::terminate() → juce::MessageQueue::runLoopSourceCallback(void*)
        → __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ → …
      

      i.e. something escapes a MessageManager::callAsync callback. The stack is already unwound, so the report names no culprit — which is why this has been hard to pin down.

      Correlation. Every occurrence I could attribute happened during GET /api/testing/screenshot. Two crashes today were directly on that call; two earlier sessions logged 500 — /api/testing/screenshot?moduleId=Interface&outputPath=… shortly before dying. It hits both the cropped (&id=) and the plain moduleId‑only form.

      Root cause (hi_backend/backend/ai_tools/RestHelpers.cpp:2539).

      SafeAsyncCall::callAsyncIfNotOnMessageThread<...>(*sp, [&](...)   // fire-and-forget
      {
          hise::ScriptContentComponent component(&spp);
          capturedImage = component.createComponentSnapshot(cropBounds, true, scale);
          captureSuccess = capturedImage.isValid();
          captureComplete.signal();
      });
      
      if (!captureComplete.wait(1000))
          return req->fail(500, "screenshot capture timed out");   // ← stack frame dies here
      

      callAsyncIfNotOnMessageThread is MessageManager::callAsync (MiscToolClasses.h:87) — it does not wait. The lambda captures capturedImage, captureSuccess and captureComplete by reference off the REST thread's stack. When the message thread takes longer than 1000 ms, the handler returns, that frame is destroyed, and the lambda then writes into freed stack memory and signals a destroyed WaitableEvent. Hence: 500 first, abort shortly after.

      1000 ms is easy to blow — the lambda constructs a fresh ScriptContentComponent and paints every script component, including all custom paint routines, on a 1600×900 interface.

      Same pattern, two more sites: RestHelpers.cpp:2725 (testing/e2e, 30 s) and :6508 (dsp/screenshot, 1500 ms — and that lambda spends 500 ms in runDispatchLoopUntil before capturing, re-entering the message loop).

      Suggested fix. SafeAsyncCall::callAsyncAndWait already exists in the same header (MiscToolClasses.h:104) and is documented to return false on timeout/deletion. Failing that: heap‑allocate the shared state in a ReferenceCountedObject/shared_ptr captured by value, so a timed‑out request cannot outlive it. A try/catch around the lambda body would also convert the abort into a 500.


      Two caveats so you can present it accurately: the other 5 SIGABRTs I can't attribute to the screenshot endpoint from the crash file alone — the unwound stack gives nothing. And the dangling-frame mechanism is provable from the code and matches the 500-then-crash sequence, but I couldn't confirm which exception actually terminates, only that one escapes a message-thread callback.

      Hise made me an F5 dude, any other app just suffers...

      David HealeyD 1 Reply Last reply Reply Quote 1
      • David HealeyD
        David Healey @ustk
        last edited by

        @ustk what's a Claude screenshot?

        Free HISE Bootcamp Full Course for beginners.
        YouTube Channel - HISE tutorials
        My Patreon - More HISE tutorials

        ustkU 1 Reply Last reply Reply Quote 0
        • ustkU
          ustk @David Healey
          last edited by

          @David-Healey When Claude makes modifications on UI, it makes a screenshot using the interaction test window to see if it renders as it should

          Hise made me an F5 dude, any other app just suffers...

          dannytaurusD 1 Reply Last reply Reply Quote 1
          • dannytaurusD
            dannytaurus @ustk
            last edited by dannytaurus

            @ustk Same here, but I don't do UI interaction loops with Claude much so it hasn't affected me.

            Post a bug on GitHub.

            My Claude says:

            +1, and confirmed on current develop (ca472e27b, 2026-08-19): the pattern ustk describes is still there at all three sites. For what it's worth the code is from February (30c732d9b, "added Rest API endpoint /screenshot"), so I don't think the recent commits introduced it, they just make the message thread busier so the 1 s timeout trips more often.

            One note on the suggested fixes: switching to SafeAsyncCall::callAsyncAndWait on its own isn't enough. It heap-allocates the WaitableEvent (so the signal() on a dead event goes away), but the lambda still captures capturedImage and captureSuccess by reference, so a late-running lambda still writes into the handler's dead stack frame. The state the lambda writes to has to outlive the handler too. A shared_ptr to a small result struct, captured by value, does it:

            struct ScreenshotCapture
              {
                  Image image;
                  bool success = false;
                  WaitableEvent done;
              };
            
              auto capture = std::make_shared<ScreenshotCapture>();
              auto sp = dynamic_cast<ProcessorWithScriptingContent*>(jp);
            
              SafeAsyncCall::callAsyncIfNotOnMessageThread<ProcessorWithScriptingContent>(*sp,
                  [capture, fullBounds, cropBounds, scale](ProcessorWithScriptingContent& spp)
              {
                  hise::ScriptContentComponent component(&spp);
                  component.setBounds(fullBounds);
                  capture->image = component.createComponentSnapshot(cropBounds, true, scale);
                  capture->success = capture->image.isValid();
                  capture->done.signal();
              });
            
              if (!capture->done.wait(1000))
                  return req->fail(500, "screenshot capture timed out");
            
              if (!capture->success)
                  return req->fail(500, "failed to capture screenshot");
            
              auto capturedImage = capture->image;
            

            If the wait times out, the lambda still runs later and writes into the heap struct, which is kept alive by its own copy of the shared_ptr. The handler has already returned its 500 and nothing points at the stack any more. (If the processor was deleted in the meantime, SafeAsyncCaller already skips the call via the WeakReference, so the event is never signalled, which is fine because the waiter has already given up.)

            Same treatment for the other two:

            • handleTestingInteraction (~line 2725): testResult + executed + completed into a shared struct, capture by value.
            • /api/dsp/screenshot (~line 6508): same as the screenshot site. Note this one also calls mm->runDispatchLoopUntil(500) inside the lambda, so it spends a third of its 1500 ms budget by design; might be worth bumping that timeout while you're in there.

            HISE dev latest / super.engineering + Claude Fable / Figma + Affinity
            Meat Beats: https://meatbeats.com
            Klippr Video: https://klippr.video

            ustkU 1 Reply Last reply Reply Quote 1
            • ustkU
              ustk @dannytaurus
              last edited by ustk

              @dannytaurus said in Claude screenshot crashes Hise:

              so I don't think the recent commits introduced it, they just make the message thread busier so the 1 s timeout trips more often.

              Yes that's it, in fact it was crashing less before for the mere reason that the project I am working on at the moment is way bigger. UI takes long time to recompile, that's the trigger...

              So in the end it just needs 2-3000 sec timeout, should be enough for most cases...

              I'll test and make a PR when I have a minute 😉

              Hise made me an F5 dude, any other app just suffers...

              1 Reply Last reply Reply Quote 1
              • First post
                Last post

              8

              Online

              2.5k

              Users

              13.9k

              Topics

              121.3k

              Posts