{"record":{"id":"7af953e8b73b1779","repo":"ytti/oxidized","slug":"hook-channel-is-required-7af953","errorCode":null,"errorMessage":"hook.channel is required","messagePattern":"hook\\.channel is required","errorType":"validation","errorClass":"KeyError","httpStatus":null,"severity":"error","filePath":"lib/oxidized/hook/xmppdiff.rb","lineNumber":46,"sourceCode":"      end\n    rescue Timeout::Error\n      logger.info \"timed out\"\n      @client = nil\n      @muc = nil\n    end\n\n    @client.on_exception do\n      logger.info \"XMPP connection aborted, reconnecting\"\n      @client = nil\n      @muc = nil\n      connect\n    end\n  end\n\n  def validate_cfg!\n    raise KeyError, 'hook.jid is required' unless cfg.has_key?('jid')\n    raise KeyError, 'hook.password is required' unless cfg.has_key?('password')\n    raise KeyError, 'hook.channel is required' unless cfg.has_key?('channel')\n    raise KeyError, 'hook.nick is required' unless cfg.has_key?('nick')\n  end\n\n  def run_hook(ctx)\n    return unless ctx.node\n    return unless ctx.event.to_s == \"post_store\"\n\n    begin\n      Timeout.timeout(15) do\n        gitoutput = ctx.node.output.new\n        diff = gitoutput.get_diff ctx.node, ctx.node.group, ctx.commitref, nil\n\n        interesting = diff[:patch].lines.to_a[4..-1].any? do |line|\n          [\"+\", \"-\"].include?(line[0]) && (not [\"#\", \"!\"].include?(line[1]))\n        end\n\n        if interesting\n          connect if @muc.nil?","sourceCodeStart":28,"sourceCodeEnd":64,"githubUrl":"https://github.com/ytti/oxidized/blob/687ed4262d9d21e54662e872e2383386d8498f7b/lib/oxidized/hook/xmppdiff.rb#L28-L64","documentation":"The xmppdiff hook validates at startup that jid, password, channel and nick are all present; a missing channel raises KeyError 'hook.channel is required' (lib/oxidized/hook/xmppdiff.rb:46). The channel is the MUC room the hook joins; connect builds the MUC JID as cfg.channel + '/' + cfg.nick via Jabber::MUC::SimpleMUCClient#join, so it must be the full room JID, e.g. netdev@conference.example.com.","triggerScenarios":"A hooks block with type: xmppdiff that omits channel: (for example jid/password/nick were copied but the room was not), or a channel key lost to indentation or renamed to room.","commonSituations":"Copying a partial example; using a bare room name without the conference domain, which passes validation but fails to join at runtime; editing the hooks block and breaking indentation.","solutions":["Add channel: netdev@conference.example.com (full MUC room JID) under the xmppdiff hook block","Keep jid, password, channel, nick at the same indentation level","Restart oxidized and confirm the log shows 'Joined <channel>'"],"exampleFix":"# before\nhooks:\n  xmpp:\n    type: xmppdiff\n    jid: oxidized@xmpp.example.com\n    password: secret\n    nick: oxidized\n\n# after\nhooks:\n  xmpp:\n    type: xmppdiff\n    jid: oxidized@xmpp.example.com\n    password: secret\n    channel: netdev@conference.example.com\n    nick: oxidized","handlingStrategy":"validation","validationCode":"# check the xmppdiff block before oxidized loads it\nhook_cfg = YAML.load_file('/etc/oxidized/config')['hooks']['xmpp']\nmissing = %w[jid password channel nick].reject { |k| hook_cfg.key?(k) }\nraise ArgumentError, \"xmppdiff config missing: #{missing.join(', ')}\" unless missing.empty?","typeGuard":"def xmppdiff_cfg_valid?(cfg)\n  cfg.is_a?(Hash) && %w[jid password channel nick].all? { |k| cfg[k].is_a?(String) && !cfg[k].empty? }\nend","tryCatchPattern":"begin\n  hook.validate_cfg!\nrescue KeyError => e\n  abort \"fix the hooks config: #{e.message}\"\nend","preventionTips":["Store channel as the full room JID (room@conference.example.com) to avoid runtime join failures","Key-check all four xmppdiff keys in the config pipeline","Smoke-test the MUC join with an XMPP client before rollout"],"tags":["oxidized","xmppdiff-hook","xmpp","muc","config-validation","keyerror"],"backgroundTag":"missing-config-key","analyzedSha":"687ed4262d9d21e54662e872e2383386d8498f7b","analyzedAt":"2026-08-23T11:19:42.084Z","schemaVersion":2},"datasetVersion":"2026-08-23T16:17:53.355Z"}