grafana/k6 · critical

extension already registered: %s

Error message

extension already registered: %s

What it means

ext.Register keeps a per-type map keyed by extension name. Registering a second extension of the same type under an already-used name trips this panic during init, before any test runs — the registry deliberately refuses silent overrides so two different modules cannot masquerade under one import path.

Source

Thrown at ext/ext.go:72

func (e Extension) String() string {
	return fmt.Sprintf("%s %s, %s [%s]", e.Path, e.Version, e.Name, e.Type)
}

// Register a new extension with the given name and type. This function will
// panic if an unsupported extension type is provided, or if an extension of the
// same type and name is already registered.
func Register(name string, typ ExtensionType, mod any) {
	mx.Lock()
	defer mx.Unlock()

	exts, ok := extensions[typ]
	if !ok {
		panic(fmt.Sprintf("unsupported extension type: %T", typ))
	}

	if _, ok := exts[name]; ok {
		panic(fmt.Sprintf("extension already registered: %s", name))
	}

	path, version := extractModuleInfo(mod)

	exts[name] = &Extension{
		Name:    name,
		Type:    typ,
		Module:  mod,
		Path:    path,
		Version: version,
	}
}

// Get returns all extensions of the specified type.
func Get(typ ExtensionType) map[string]*Extension {
	mx.RLock()
	defer mx.RUnlock()

View on GitHub (pinned to 93accf6570)

Solutions

  1. Give each extension a unique name — conventionally its full import path (k6/x/...), which makes collisions structurally unlikely
  2. Remove the stale copy: go mod tidy / clean the xk6 build pins so only one version of the module is linked
  3. If the duplicate comes from a vendored fork, rename the fork's registered name in its init()
  4. Rebuild the binary and verify with k6 version / k6 inspect that each extension appears once

Example fix

// before (two packages both do)
func init() { ext.Register("k6/x/queue", ext.JSExtension, &Q{}) } // second one panics at startup

// after (fork renamed)
func init() { ext.Register("k6/x/queue-fork", ext.JSExtension, &Q{}) }
Defensive patterns

Strategy: validation

Validate before calling

// Go, guard before registering
func registerOnce(name string, typ ext.ExtensionType, mod any) {
  if _, dup := ext.Get(typ)[name]; dup { // note: panics on bad typ; validate it first
    panic(fmt.Sprintf("extension %q already registered - rename this one", name))
  }
  ext.Register(name, typ, mod)
}

Prevention

When it happens

Trigger: Two xk6 extensions in one binary both calling ext.Register("k6/x/mymod", ext.JSExtension, ...); an extension registered from two init() paths (e.g. the package linked twice via different import paths after a rename); a fork reusing an existing extension's name; output extensions colliding on a name like "custom".

Common situations: k6 binary built with two extensions where one is a vendored fork/older copy of the other (common after xk6 build flag drift); module moved to a new path but old path still imported somewhere; accidental double registration when a file is duplicated in the extensions tree; CI building with stale extension pins.

Related errors


AI-assisted analysis of grafana/k6@93accf6570 (2026-08-15). Data as JSON: /api/errors/a1c3e9c28b22b034. Report an issue: GitHub.