agalwood/Motrix · error · AppError

EngineProcessOwnershipUnverified

EngineProcessOwnershipUnverified

Error message

The process changed or is not verified as Motrix-owned

What it means

Thrown as an AppError (code EngineProcessOwnershipUnverified) by Aria2ProcessManager.forceTerminateVerified when the process currently listening on the given port cannot be verified as safe to terminate. The method inspects the port, then checks three conditions: processInfo exists, its PID matches expectedPid, and processInfo.safeToTerminate is true. If any fails, it refuses to kill — this is a safety guard to prevent terminating an unrelated or external process.

Source

Thrown at src/core/engine/aria2/aria2-process-manager.ts:252

    const ownership =
      inspected.name === 'unknown'
        ? EngineProcessOwnership.Unknown
        : EngineProcessOwnership.Other
    return this.toProcessInfo(inspected, ownership, false)
  }

  async forceTerminateVerified(
    expectedPid: number,
    port: number,
    expected: ExpectedAria2Process
  ): Promise<void> {
    const processInfo = await this.inspectPort(port, expected)
    if (
      !processInfo ||
      processInfo.pid !== expectedPid ||
      !processInfo.safeToTerminate
    ) {
      throw new AppError(
        ErrorCode.EngineProcessOwnershipUnverified,
        'The process changed or is not verified as Motrix-owned'
      )
    }

    if (
      processInfo.ownership === EngineProcessOwnership.CurrentApp &&
      this.getPid() === expectedPid
    ) {
      this.kill()
    } else {
      this.inspector.forceTerminate(expectedPid)
    }

    const deadline = Date.now() + 5_000
    while (this.inspector.isAlive(expectedPid) && Date.now() < deadline) {
      await new Promise<void>((resolve) => setTimeout(resolve, 100))
    }

View on GitHub (pinned to 1a708ee577)

Solutions

  1. Re-check the current engine status before attempting force termination — the process may have already exited
  2. Call inspectPort to see what is actually on the port and its ownership classification
  3. If the process is ExternalAria2 or Other, do not force-kill it — manually stop the external process or switch to a different RPC port instead
  4. Update expectedPid from the latest engine status before retrying
Defensive patterns

Strategy: try-catch

Validate before calling

async function isSafeToTerminate(processManager: Aria2ProcessManager, port: number, expectedPid: number, expected: ExpectedAria2Process): Promise<boolean> {
  const info = await processManager.inspectPort(port, expected)
  return info !== null && info.pid === expectedPid && info.safeToTerminate
}

Try / catch

try {
  await processManager.forceTerminateVerified(expectedPid, port, expected)
} catch (e) {
  if (e instanceof AppError && e.code === ErrorCode.EngineProcessOwnershipUnverified) {
    // Process changed or isn't ours — use SwitchPort recovery instead
    await supervisor.recover({ action: EngineRecoveryAction.SwitchPort })
  } else throw e
}

Prevention

When it happens

Trigger: forceTerminateVerified(expectedPid, port, expected) is called; inspectPort returns null (nothing listening), or the PID on the port differs from expectedPid (process changed/died and something else took the port), or the process is not safeToTerminate (it's an external aria2, unknown process, or other application).

Common situations: The aria2 process already exited naturally and a different process (or nothing) now holds the port; a non-Motrix application is using the port; the expectedPid is stale because the engine restarted with a new PID; ownership record expired or was cleared; an external aria2 instance (not launched by this app) is on the port.

Related errors


AI-assisted analysis of agalwood/Motrix@1a708ee577 (2026-08-12). Data as JSON: /api/errors/03b933e42cbc47ed. Report an issue: GitHub.