{"record":{"id":"8eed2f0cab64cc47","repo":"ytti/oxidized","slug":"hook-token-is-required","errorCode":null,"errorMessage":"hook.token is required","messagePattern":"hook\\.token is required","errorType":"validation","errorClass":"KeyError","httpStatus":null,"severity":"error","filePath":"lib/oxidized/hook/slackdiff.rb","lineNumber":10,"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","sourceCodeStart":1,"sourceCodeEnd":28,"githubUrl":"https://github.com/ytti/oxidized/blob/687ed4262d9d21e54662e872e2383386d8498f7b/lib/oxidized/hook/slackdiff.rb#L1-L28","documentation":"The slackdiff hook validates at startup that both token and channel keys exist in its config; a missing token raises KeyError 'hook.token is required' (lib/oxidized/hook/slackdiff.rb:10) while hooks load, aborting startup. The token is a Slack API token (xoxb-... bot or xoxp-... user token) that slack_ruby_client uses for auth_test, snippet upload, and chat.postMessage.","triggerScenarios":"A hooks block with type: slackdiff that lacks token: (only channel set), or a token key that is misspelled or indented so it never lands in the hook's cfg hash.","commonSituations":"Copying the slackdiff example and forgetting the secret; intending to inject the token from an env var or secret store but leaving the literal key out of the deployed config; YAML indentation errors after adding messageformat or proxy keys.","solutions":["Add the token under the slackdiff hook block: token: xoxb-1234-abcdef (inject it from your secrets manager at deploy time, do not commit it)","Confirm both token and channel sit 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    channel: \"#netdev-changes\"\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":["Inject the token from a secrets manager at deploy time so the key is never accidentally dropped","Use Slack bot tokens with minimal scopes and rotate them on a schedule","Key-check the hooks section in CI"],"tags":["oxidized","slackdiff-hook","slack","api-token","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"}