{"record":{"id":"f14473ff5e76883b","repo":"ytti/oxidized","slug":"hook-channel-is-required","errorCode":null,"errorMessage":"hook.channel is required","messagePattern":"hook\\.channel is required","errorType":"validation","errorClass":"KeyError","httpStatus":null,"severity":"error","filePath":"lib/oxidized/hook/slackdiff.rb","lineNumber":11,"sourceCode":"require 'slack_ruby_client'\nrequire 'uri'\nrequire 'net/http'\n\n# defaults to posting a diff, if messageformat is supplied them a message will be posted too\n# diff defaults to true\n\nclass SlackDiff < Oxidized::Hook\n  def validate_cfg!\n    raise KeyError, 'hook.token is required' unless cfg.has_key?('token')\n    raise KeyError, 'hook.channel is required' unless cfg.has_key?('channel')\n  end\n\n  def slack_upload(client, title, content, channel, proxy)\n    logger.info \"Posting diff as snippet to #{channel}\"\n    upload_dest = client.files_getUploadURLExternal(filename:     \"change\",\n                                                    length:       content.length,\n                                                    snippet_type: \"diff\")\n    file_uri = URI.parse(upload_dest[:upload_url])\n\n    proxy_uri = URI.parse(proxy) if proxy\n    proxy_address = proxy_uri ? proxy_uri.host : :ENV\n    proxy_port = proxy_uri&.port\n    proxy_user = proxy_uri&.user\n    proxy_pass = proxy_uri&.password\n\n    http = Net::HTTP.new(file_uri.host, file_uri.port, proxy_address, proxy_port, proxy_user, proxy_pass)\n    http.use_ssl = true\n","sourceCodeStart":1,"sourceCodeEnd":29,"githubUrl":"https://github.com/ytti/oxidized/blob/687ed4262d9d21e54662e872e2383386d8498f7b/lib/oxidized/hook/slackdiff.rb#L1-L29","documentation":"The slackdiff hook validates at startup that both token and channel keys exist in its config; a missing channel raises KeyError 'hook.channel is required' (lib/oxidized/hook/slackdiff.rb:11). The channel is where diffs are posted as snippets and where optional messages go (client.files_completeUploadExternal / chat.postMessage), so the token's bot/user must be able to post there.","triggerScenarios":"A hooks block with type: slackdiff that sets token: but omits channel:, or a channel key lost to a typo or bad YAML indentation when the block was edited.","commonSituations":"Copying a partial example; renaming channel to room; reindenting the hooks block while adding messageformat/diff/proxy keys.","solutions":["Add the channel under the slackdiff hook block: channel: \"#netdev-changes\" (or a channel ID like C0123456789)","Keep token and channel at the same indentation as the hook's other keys","Restart oxidized so hook validation runs again"],"exampleFix":"# before\nhooks:\n  slack:\n    type: slackdiff\n    token: xoxb-1234567890-abcdef\n\n# after\nhooks:\n  slack:\n    type: slackdiff\n    token: xoxb-1234567890-abcdef\n    channel: \"#netdev-changes\"","handlingStrategy":"validation","validationCode":"# check the slackdiff block before oxidized loads it\nhook_cfg = YAML.load_file('/etc/oxidized/config')['hooks']['slack']\nmissing = %w[token channel].reject { |k| hook_cfg.key?(k) }\nraise ArgumentError, \"slackdiff config missing: #{missing.join(', ')}\" unless missing.empty?","typeGuard":"def slackdiff_cfg_valid?(cfg)\n  cfg.is_a?(Hash) && %w[token channel].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":["Prefer channel IDs (C0123456789) over names: they survive renames and are unambiguous","Invite the bot to private channels; otherwise the post fails at runtime even though validation passed","Key-check the hooks section in CI"],"tags":["oxidized","slackdiff-hook","slack","channel","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"}