title: Node forever says program is running, but evidence suggests not
body: <p>I'm attempting to run a simple twitterbot using the <a href="https://github.com/ttezel/twit" rel="nofollow noreferrer">twit</a> library and <a href="https://github.com/foreverjs/forever" rel="nofollow noreferrer">forever</a>. The bot essentially watches another twitter account, and copies its tweets with some modifications.</p>
  
  <p>I have been logging the tweets that are received from the streaming API, and also logging any errors.</p>
  
  <p>However, the bot will often stop working, while logging no error, while <code>forever list</code> still shows the program as running.</p>
  
  <pre><code>info:    Forever processes running
  data:        uid  command         script forever pid   id logfile                        uptime        
  data:    [0] QOmt /usr/bin/nodejs bot.js 13642   13649    /home/ubuntu/.forever/QOmt.log 0:5:25:34.733
  </code></pre>
  
  <p>At first, I thought this could be a problem with the twitter streaming API - i.e., that information had stopped being sent "down the pipe", as the log files had ceased to record new messages from the twitter streaming API.</p>
  
  <p>In order to verify that the bot was indeed still running, I created a <code>heartbeat</code> that logs every minute.</p>
  
  <pre><code>// Heartbeat to make sure the process is still running
  setInterval(function () {
    console.logWithDate('Program heartbeat');
  }, 60*1000);
  </code></pre>
  
  <p>When the bot stops functioning, the heartbeat also stops logging. Yet <code>forever</code> still says the bot is running.</p>
  
  <p>Any help would be very much appreciated. I post the full code of my bot below in case it is useful.</p>
  
  <pre><code>#!/usr/bin/env node
  "use strict"
  
  const fs = require('fs');
  const util = require('util');
  const Twit = require('twit');
  
  // Logging
  const error_log_file = fs.createWriteStream(__dirname + '/debug.log', {flags : 'a'});
  const log_stdout = process.stdout;
  
  console.error = function(d) {
    error_log_file.write(util.format("\n%s&gt; %s", new Date(), d) + '\n');
    log_stdout.write(util.format("\n%s&gt; %s", new Date(), d) + '\n');
  };
  
  console.logWithDate = function(d) {
      log_stdout.write(util.format("\n%s&gt; %s", new Date(), d) + '\n');
  }
  
  // Heartbeat to make sure the process is still running
  setInterval(function () {
    console.logWithDate('Program heartbeat');
  }, 60*1000);
  
  
  // Read in twitter secrets file
  const twitter_secrets = JSON.parse(fs.readFileSync("twitter_secrets.json"));
  
  
  // Connect to twitter
  const client = new Twit({
      consumer_key: twitter_secrets.TWITTER_CONSUMER_KEY,
      consumer_secret: twitter_secrets.TWITTER_CONSUMER_SECRET,
      access_token: twitter_secrets.TWITTER_ACCESS_TOKEN_KEY,
      access_token_secret: twitter_secrets.TWITTER_ACCESS_TOKEN_SECRET,
      timeouts_ms: 60*1000
  });
  
  // Main
  const stream = client.stream('statuses/filter', {follow: 87818409});
  stream.on('tweet', function(event) {
      if (event.user.id === 87818409) {
          console.logWithDate("Guardian tweet: " + event.text)
          client.post(
              'statuses/update',
              {status: misspellRandomWords(event.text)},
              function(error, tweet, response) {
                  if (error) {
                      console.error(error);
                  } else {
                      console.logWithDate("Bot tweet: " + tweet.text);  // Tweet body.
                      //console.log(response);  // Raw response object.
                  }
              }
          );
      } else {
          console.logWithDate("Guardian-related tweet: " + event.text)
      }
  });
  
  // Log various types of messages for debugging
  stream.on('limit', function(error) {
      console.error(error);
  });
  
  stream.on('disconnect', function(error) {
      console.error(error);
  });
  
  stream.on('error', function(error) {
      console.error(error);
  });
  
  stream.on('connect', function (conn) {
    console.logWithDate('connecting')
  })
  
  stream.on('reconnect', function (reconn, res, interval) {
    console.logWithDate('reconnecting. statusCode:', res.statusCode)
  })
  
  
  /* Helper functions */
  
  function swapRandomLetters(word) {
      const limit = word.length;
      const iFirstLetter = Math.floor(Math.random() * limit);
      var iSecondLetter = Math.floor(Math.random() * limit);
  
      while (iFirstLetter === iSecondLetter) {
          iSecondLetter = Math.floor(Math.random() * limit);
      }
  
      let letters = word.split("");
      letters[iFirstLetter] = word[iSecondLetter];
      letters[iSecondLetter] = word[iFirstLetter];
      return letters.join("");
  }
  
  function isLink(word) {
      // Very crude URL check
      return word.substring(0,4) === "http";
  }
  
  function misspellRandomWords(sentence) {
      let words = sentence.split(" ");
      const limit = words.length;
  
      // Choose a first word, filtering out urls
      var iFirstWord = Math.floor(Math.random() * limit);
      while (isLink(words[iFirstWord]) || words[iFirstWord][0] === "@" ) {
          iFirstWord = Math.floor(Math.random() * limit);
      }
  
      // Choose second misspelled word, and make sure it isn't the first or an URL
      var iSecondWord = Math.floor(Math.random() * limit);
      while (isLink(words[iSecondWord]) ||
              iSecondWord === iFirstWord ||
              words[iSecondWord][0] === "@") {
          iSecondWord = Math.floor(Math.random() * limit);
      }
  
      words[iFirstWord] = swapRandomLetters(words[iFirstWord]);
      words[iSecondWord] = swapRandomLetters(words[iSecondWord]);
  
      return words.join(" ");
  }
  </code></pre>