{"record":{"id":"a6698456160decb0","repo":"instructure/canvas-lms","slug":"error-running-python-qti-converter-output","errorCode":null,"errorMessage":"Error running python qti converter: #{output}","messagePattern":"Error running python qti converter: #(.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"gems/plugins/qti_exporter/lib/qti.rb","lineNumber":107,"sourceCode":"\n  def self.convert_xml(xml, opts = {})\n    assessments = nil\n    questions = nil\n    Dir.mktmpdir do |dirname|\n      xml_file = File.join(dirname, opts[:file_name] || \"qti.xml\")\n      File.open(xml_file, \"w\") { |f| f << xml }\n\n      # convert to 2.1\n      dest_dir_2_1 = File.join(dirname, \"qti_2_1\")\n      command = Qti.get_conversion_command(dest_dir_2_1, dirname)\n      output = `#{command}`\n\n      if $?.exitstatus == 0\n        manifest = File.join(dest_dir_2_1, \"imsmanifest.xml\")\n        questions = convert_questions(manifest, opts)\n        assessments = convert_assessments(manifest, opts)\n      else\n        raise \"Error running python qti converter: #{output}\"\n      end\n    end\n    [questions, assessments]\n  end\n\n  def self.convert_files(manifest_path)\n    attachments = []\n    doc = Nokogiri::XML(File.open(manifest_path))\n    resource_nodes = doc.css(\"resource\")\n    doc.css(\"file\").each do |file|\n      # skip resource nodes, which are things like xml metadata and other sorts\n      next if resource_nodes.any? { |node| node[\"href\"] == file[\"href\"] }\n\n      # anything left is a file that needs to become an attachment on the context\n      attachments << CGI.unescape(file[\"href\"])\n    end\n    attachments\n  end","sourceCodeStart":89,"sourceCodeEnd":125,"githubUrl":"https://github.com/instructure/canvas-lms/blob/1c9f0bb8013ed69c4f2efe11fd483025469b7e6c/gems/plugins/qti_exporter/lib/qti.rb#L89-L125","documentation":"The QTI module shells out to the python QTI converter and checks $?.exitstatus after conversion. A non-zero exit means the converter failed; the raw converter output is embedded in the raised message so operators can diagnose the underlying python-side failure.","triggerScenarios":"Running canvas_migrate_qti (conversion in qti.rb) where the python converter subprocess exits non-zero — invalid/corrupt QTI package input, missing python dependencies for the converter, wrong python version, unreadable source files, or converter crash on a specific item type.","commonSituations":"Importing a QTI zip exported from another LMS with unsupported question types; converter's python dependencies not installed on a new host; QTI package with malformed XML; disk/permission issues in the temp conversion directories.","solutions":["Read the converter output in the error message for the actual python traceback / failure cause.","Validate the source QTI package (unzip it, check imsmanifest.xml and XML well-formedness) and re-export from the source LMS if corrupt.","Ensure the python converter's dependencies and correct python version are installed on the job host.","Retry the migration; if a specific question type crashes the converter, remove or convert that item manually first.","Check permissions/writability of the temp directories used for the conversion."],"exampleFix":"// before\n# converter crashes: missing lxml in python env\n// after (on job host)\npython3 -m pip install lxml   # converter dependency\n# re-run the QTI import; exit status becomes 0","handlingStrategy":"try-catch","validationCode":"# pre-flight: verify converter binary and deps\nsystem('python3', '-c', 'import lxml') or raise 'qti converter python deps missing'","typeGuard":null,"tryCatchPattern":"begin\n  questions, assessments = Qti.migrate_tqi(src, dest, opts)\nrescue RuntimeError => e\n  raise unless e.message.start_with?('Error running python qti converter:')\n  logger.error(e.message) # output contains the python traceback\n  raise MigrationError, 'QTI conversion failed; see logs for converter output'\nend","preventionTips":["Install and pin the converter's python dependencies on migration job hosts.","Validate QTI packages (zip integrity, well-formed XML) before importing.","Capture and persist converter stderr for post-mortems.","Retry failed imports once before surfacing errors to users."],"tags":["qti","python","subprocess","command-failed","import"],"backgroundTag":"git-command-failed","analyzedSha":"1c9f0bb8013ed69c4f2efe11fd483025469b7e6c","analyzedAt":"2026-09-15T20:33:18.891Z","contentChangedAt":"2026-09-15T20:33:18.891Z","schemaVersion":2},"datasetVersion":"2026-09-23T02:17:17.105Z"}