{"record":{"id":"d88c7ddfbb00cc6a","repo":"javan/whenever","slug":"fail-can-t-cut-negative-lines-from-the-crontab","errorCode":null,"errorMessage":"[fail] Can't cut negative lines from the crontab #{options[:cut]}","messagePattern":"\\[fail\\] Can't cut negative lines from the crontab #(.+?)","errorType":"console","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"lib/whenever/command_line.rb","lineNumber":29,"sourceCode":"\n      @options[:crontab_command] ||= 'crontab'\n      @options[:file]            ||= 'config/schedule.rb'\n      @options[:cut]             ||= 0\n      @options[:identifier]      ||= default_identifier\n      @options[:console]    = true if @options[:console].nil?\n\n      if !File.exist?(@options[:file]) && @options[:clear].nil?\n        warn(\"[fail] Can't find file: #{@options[:file]}\")\n        return_or_exit(false)\n      end\n\n      if [@options[:update], @options[:write], @options[:clear]].compact.length > 1\n        warn(\"[fail] Can only update, write or clear. Choose one.\")\n        return_or_exit(false)\n      end\n\n      unless @options[:cut].to_s =~ /[0-9]*/\n        warn(\"[fail] Can't cut negative lines from the crontab #{options[:cut]}\")\n        return_or_exit(false)\n      end\n      @options[:cut] = @options[:cut].to_i\n\n      @timestamp = Time.now.to_s\n    end\n\n    def run\n      if @options[:update] || @options[:clear]\n        write_crontab(updated_crontab)\n      elsif @options[:write]\n        write_crontab(whenever_cron)\n      else\n        puts Whenever.cron(@options)\n        puts \"## [message] Above is your schedule file converted to cron syntax; your crontab file was not updated.\"\n        puts \"## [message] Run `whenever --help' for more options.\"\n        return_or_exit(true)\n      end","sourceCodeStart":11,"sourceCodeEnd":47,"githubUrl":"https://github.com/javan/whenever/blob/756163ed1aa928c8abd629aa2f4904bc5226538c/lib/whenever/command_line.rb#L11-L47","documentation":"The --cut/-k option strips N lines from the top of the existing crontab before updating (used to drop headers such as 'DO NOT EDIT THIS FILE'). The guard at command_line.rb:28-31 intends to reject negative cut values, but its regex /[0-9]*/ matches zero-or-more digits and therefore matches ANY string, including '-3' and the empty string — so as shipped this warning can never fire. Additionally, bin/whenever converts --cut to an Integer via lines.to_i before the check, so a negative value slips through and silently slices from the end of the crontab instead (split(...)[-3..-1] keeps the last 3 lines).","triggerScenarios":"Computing the cut dynamically as (total_lines - lines_to_keep), which goes negative on short crontabs; passing user-supplied values to -k; running patched or vendored whenever builds where the regex was corrected to something like /\\A\\d+\\z/, in which case cut: '-3' does trigger the warning and exit 1.","commonSituations":"Scripts migrating system crontabs (stripping the 3-line Debian 'DO NOT EDIT' header) where the line count assumption breaks; automation that derives cut from wc -l output; forks or vendor copies with a stricter guard than upstream.","solutions":["Pass a non-negative integer to --cut / :cut (0 or more lines).","Clamp computed values: use [0, computed_cut].max in the script that builds the command.","If you maintain a fork, fix the guard to /\\A\\d+\\z/ so invalid values are actually rejected instead of silently accepted."],"exampleFix":"# before (goes negative on short crontabs)\nwhenever --update-crontab -k $((total_lines - keep))\n\n# after\nwhenever --update-crontab -k $(( total_lines - keep > 0 ? total_lines - keep : 0 ))","handlingStrategy":"validation","validationCode":"cut = Integer(value) rescue nil\nabort('--cut must be a non-negative integer') unless cut && cut >= 0","typeGuard":"def valid_cut?(value)\n  value.is_a?(Integer) && value >= 0\nend","tryCatchPattern":"result = Whenever::CommandLine.execute(opts.merge(console: false))\nabort('whenever rejected the cut option') unless result.zero?","preventionTips":["Clamp computed cut values with [0, computed].max before passing them to -k or :cut.","Note the upstream guard at command_line.rb:28 never actually fires (its /[0-9]*/ regex matches every string), so validate on your side.","In vendored or patched copies, tighten the regex to /\\A\\d+\\z/ so invalid cut values fail loudly."],"tags":["ruby","cli","cron","input-validation","dead-code"],"backgroundTag":"invalid-cli-option-value","analyzedSha":"756163ed1aa928c8abd629aa2f4904bc5226538c","analyzedAt":"2026-08-21T18:07:39.974Z","schemaVersion":2},"datasetVersion":"2026-08-21T18:17:14.833Z"}