greensock/GSAP · warning

Error: GSDevTools can only have one instance that's globally

Error message

Error: GSDevTools can only have one instance that's globally synchronized.

What it means

GSDevTools synchronizes all instances through a single hidden root tween; only one instance can own that global sync at a time. Creating a second instance with vars.globalSync (true by default, or when animating the _recordedRoot) warns that the previous instance loses sync ownership.

Source

Thrown at src/GSDevTools.js:790

						declaredAnimation.resume();
						linkedAnimation.kill();
					}
				}
				inProgress = selectedAnimation._inProgress || 0;
				outProgress = selectedAnimation._outProgress || 100;
				inPoint.style.left = inProgress + "%";
				outPoint.style.left = outProgress + "%";
				if (_fullyInitialized) { //don't record inProgress/outProgress unless we're fully instantiated because people may call GSDevTools.create() before creating/defining their animations, thus the inTime/outTime may not exist yet.
					record("animation", selectedAnimation.vars.id);
					record("in", inProgress);
					record("out", outProgress);
				}
				startTime = 0;
				maxDuration = vars.maxDuration || Math.min(1000, _getClippedDuration(selectedAnimation));
				if (selectedAnimation === _recordedRoot || vars.globalSync) {
					_merge();
					linkedAnimation = _rootTween;
					_rootInstance && _rootInstance !== _self && console.warn("Error: GSDevTools can only have one instance that's globally synchronized.");
					_rootInstance = _self;
					if (selectedAnimation !== _recordedRoot) {
						tl = selectedAnimation;
						endTime = tl.totalDuration();
						if (endTime > 99999999) { //in the case of an infinitely repeating animation, just use a single iteration's duration instead.
							endTime = tl.duration();
						}
						while (tl.parent) {
							startTime = (startTime / tl._ts) + tl._start;
							endTime = (endTime / tl._ts) + tl._start;
							tl = tl.parent;
						}
					} else {
						endTime = _recordedRoot.duration();
					}
					if (endTime - startTime > maxDuration) { //cap end time at 1000 because it doesn't seem reasonable to accommodate super long stuff.
						endTime = startTime + maxDuration;
					}

View on GitHub (pinned to 13e2b79054)

Solutions

  1. Kill the previous instance before creating a new one (GSDevTools.getByAnimation(animation).kill() or instance.kill())
  2. Set globalSync:false on all but one instance
  3. Guard creation so only one instance exists app-wide (module-level singleton)

Example fix

// before
class Panel { mount() { this.devTools = GSDevTools.create({ animation: tl }); } }
// after
class Panel {
  mount() {
    this.devTools?.kill?.();
    const existing = GSDevTools.getByAnimation(tl);
    if (existing) existing.kill();
    this.devTools = GSDevTools.create({ animation: tl, globalSync: false });
  }
}
Defensive patterns

Strategy: validation

Validate before calling

if (GSDevTools.getByAnimation(animation)) { /* instance exists */ } else { GSDevTools.create({ animation }); }

Type guard

const hasActiveGlobalInstance = () => !!document.querySelector('.gs-dev-tools');

Prevention

When it happens

Trigger: Calling GSDevTools.create() twice with globalSync not disabled; creating instances in multiple components/pages that each default to globalSync; leaving an old instance alive when mounting a new one (e.g. in React/HMR re-renders).

Common situations: React strict-mode or HMR double-mounting a component that calls GSDevTools.create(); opening the tool on the global timeline in several widgets on one page; SPA navigation without killing the previous instance.

Related errors


AI-assisted analysis of greensock/GSAP@13e2b79054 (2026-08-29). Data as JSON: /api/errors/e426f572004cd4c6. Report an issue: GitHub.