{"record":{"id":"c0e3248af313b570","repo":"agalwood/Motrix","slug":"engineprocessownershipunverified-c0e324","errorCode":"EngineProcessOwnershipUnverified","errorMessage":"A verified aria2 process is required for force termination","messagePattern":"A verified aria2 process is required for force termination","errorType":"error_code","errorClass":"AppError","httpStatus":null,"severity":"error","filePath":"src/core/engine/engine-supervisor.ts","lineNumber":560,"sourceCode":"\n    const previousRpcPort = this.settingsManager.getEngine().rpcPort\n\n    if (\n      request.action === EngineRecoveryAction.RestoreDefaultPort &&\n      previousRpcPort === ENGINE_RPC_PORT\n    ) {\n      return {\n        ok: this.state === EngineState.Ready,\n        previousRpcPort,\n        rpcPort: previousRpcPort,\n        status: this.getStatus(),\n      }\n    }\n\n    if (request.action === EngineRecoveryAction.ForceTerminate) {\n      const expected = this.expectedProcess()\n      if (!expected || request.expectedPid === undefined) {\n        throw new AppError(\n          ErrorCode.EngineProcessOwnershipUnverified,\n          'A verified aria2 process is required for force termination'\n        )\n      }\n      await this.processManager.forceTerminateVerified(\n        request.expectedPid,\n        previousRpcPort,\n        expected\n      )\n    }\n\n    if (request.action === EngineRecoveryAction.SwitchPort) {\n      const nextPort = await findAvailablePort(previousRpcPort + 1)\n      if (nextPort === null) {\n        throw new AppError(\n          ErrorCode.EngineStartFailed,\n          'No available RPC port was found'\n        )","sourceCodeStart":542,"sourceCodeEnd":578,"githubUrl":"https://github.com/agalwood/Motrix/blob/1a708ee57746c434e2c67a44bbf0906a976afea4/src/core/engine/engine-supervisor.ts#L542-L578","documentation":"Thrown as an AppError (code EngineProcessOwnershipUnverified) by EngineSupervisor.recover when a ForceTerminate recovery action is requested but the supervisor cannot produce a verified expected process descriptor. This fires when expectedProcess() returns null (no binaryPath or no lastStartArgs — meaning start() was never called or recorded no args) OR when request.expectedPid is undefined (the caller didn't specify which PID to terminate). This is a safety guard: force termination requires both a known process fingerprint and an explicit PID.","triggerScenarios":"recover({ action: ForceTerminate, expectedPid }) is called where expectedProcess() is null (this.binaryPath or this.lastStartArgs is empty) or request.expectedPid is not provided.","commonSituations":"The UI offers a 'force terminate' recovery button before any engine start attempt has recorded process args; the caller omits expectedPid from the recovery request; the supervisor was reset and lost its lastStartArgs; a stale recovery request arrives after a restart cycle cleared the expected process state.","solutions":["Ensure start() has been called at least once so expectedProcess() can return a descriptor (binaryPath + lastStartArgs)","Always include expectedPid in the ForceTerminate recovery request — obtain it from the last known engine status (managedPid)","If no process fingerprint exists, use SwitchPort or RestoreDefaultPort recovery instead of ForceTerminate","Check supervisor.getStatus().managedPid before offering the force-terminate option in the UI"],"exampleFix":"// before\nawait supervisor.recover({ action: EngineRecoveryAction.ForceTerminate }) // missing expectedPid\n// after\nconst pid = supervisor.getStatus().managedPid\nawait supervisor.recover({ action: EngineRecoveryAction.ForceTerminate, expectedPid: pid })","handlingStrategy":"validation","validationCode":"function canForceTerminate(supervisor: EngineSupervisor): boolean {\n  const status = supervisor.getStatus()\n  return status.managedPid !== null\n}\n// Build the request only if a PID is known:\nconst pid = supervisor.getStatus().managedPid\nif (pid === null) {\n  // Use a different recovery action\n  await supervisor.recover({ action: EngineRecoveryAction.SwitchPort })\n} else {\n  await supervisor.recover({ action: EngineRecoveryAction.ForceTerminate, expectedPid: pid })\n}","typeGuard":null,"tryCatchPattern":"try {\n  await supervisor.recover(request)\n} catch (e) {\n  if (e instanceof AppError && e.code === ErrorCode.EngineProcessOwnershipUnverified) {\n    // No verified process — fall back to port switch\n    await supervisor.recover({ action: EngineRecoveryAction.SwitchPort })\n  } else throw e\n}","preventionTips":["Only offer ForceTerminate in the UI when getStatus().managedPid is non-null","Always include expectedPid in ForceTerminate recovery requests","Prefer SwitchPort or RestoreDefaultPort when no verified process fingerprint exists"],"tags":["engine","supervisor","recovery","termination","ownership","safety"],"backgroundTag":null,"analyzedSha":"1a708ee57746c434e2c67a44bbf0906a976afea4","analyzedAt":"2026-08-12T16:18:09.346Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}