projectdiscovery/nuclei · error

nuclei js runtime: program failed to terminate after interru

Error message

nuclei js runtime: program failed to terminate after interrupt

What it means

The template's context deadline fired, goja was interrupted, but the JS program still did not terminate before the grace timer expired, so the session is abandoned (state=sessionAbandoned). The error joins errRuntimeTerminationTimeout ('nuclei js runtime: ...') with the context error. The concurrency slot is only released later, by the goroutine waiting on resultChan after the program eventually finishes.

Source

Thrown at pkg/js/compiler/session.go:180

		s.config.runtime.Interrupt(contextErr)

		timer := time.NewTimer(time.Second)
		defer timer.Stop()

		select {
		case result := <-s.resultChan:
			return result, nil
		case <-timer.C:
			return gojaRunResult{}, s.abandon(contextErr)
		}
	case result := <-s.resultChan:
		return result, nil
	}
}

func (s *session) abandon(contextErr error) error {
	s.state = sessionAbandoned
	err := fmt.Errorf("%w: %w", errRuntimeTerminationTimeout, contextErr)
	if s.config.releaseAbandonedSlot != nil {
		resultChan := s.resultChan
		releaseAbandonedSlot := s.config.releaseAbandonedSlot
		go func() {
			<-resultChan
			releaseAbandonedSlot()
		}()
	}
	if s.config.onAbandon != nil {
		s.config.onAbandon(err)
	}
	return err
}

func (s *session) cleanupCommon() {
	if s.state == sessionAbandoned || !s.commonPrepared || s.commonCleaned {
		return
	}

View on GitHub (pinned to 265b3a3dec)

Solutions

  1. Bound loops in the template's JS and break work into smaller steps so interrupts can land
  2. Move heavy iteration to native nuclei features (payloads, workflows) instead of a JS loop
  3. Raise the timeout budget for that template if the work is legitimately long
  4. Expect resource lag: abandoned sessions hold their slot until the goroutine finally exits — reduce concurrency pressure after seeing these

Example fix

// before (template code block)
while (true) { compute(next()); }

// after
for (let i = 0; i < maxItems; i++) { compute(items[i]); }
Defensive patterns

Strategy: try-catch

Validate before calling

// in template JS: make loops interruptible and bounded
const MAX = 10000;
for (let i = 0; i < items.length && i < MAX; i++) { work(items[i]); }

Try / catch

Treat errors.Is(err, errRuntimeTerminationTimeout) (message contains 'failed to terminate after interrupt') as a hung template: stop scheduling it, record the template ID, and continue other work — retrying immediately will hang again. Account for the abandoned session holding its slot until the goroutine exits.

Prevention

When it happens

Trigger: Tight CPU-bound loops in template JS (while(true){}, huge for-loops) that swallow goja's interrupt checks; long native calls that ignore interruption; a -timeout/-js-timeout budget smaller than the script's real runtime.

Common situations: Code-protocol templates with unbounded iteration; brute-force style JS loops over big wordlists inside the script instead of using nuclei' native flows; overloaded machines making valid scripts exceed the deadline repeatedly.

Related errors


AI-assisted analysis of projectdiscovery/nuclei@265b3a3dec (2026-08-15). Data as JSON: /api/errors/78b85c5f93504d0e. Report an issue: GitHub.