Scheduled function not finishing, but firing multiple times

I am trying to run a Mastodon bot using scheduled functions.

My site name is: oblique-questions-bot.netlify.app

The source code is here: GitHub - matthewmcvickar/oblique-questions-bot: A bot that posts questions without context.

I think I have the scheduled function set up correctly, all of which can be seen in that GitHub repo:

  • I am exporting a function in the root ./bot.js file called doPost().
  • I have a ./netlify.toml file with the cron time set.
  • I have a ./netlify/functions/do-post.js file that imports the doPost() function and executes it.

Unfortunately, the function is only executing in part. The text for the post is prepared, but it doesn’t connect and post to Mastodon.

Here’s the log of the most recent execution of the function. I don’t know why it executes three times, and I don’t know why it’s not going any further than preparing the status text:

Jan 29, 04:00:03 PM: a8ebbbbf INFO   ---
Jan 29, 04:00:03 PM: a8ebbbbf INFO   RUNNING SCHEDULED FUNCTION:
Jan 29, 04:00:03 PM: a8ebbbbf INFO   CALLING doPost() FUNCTION:
Jan 29, 04:00:03 PM: a8ebbbbf INFO   NOW ATTEMPTING TO POST: Bought their possessions?
Jan 29, 04:00:04 PM: a8ebbbbf Duration: 36.97 ms	Memory Usage: 107 MB	Init Duration: 419.02 ms	
Jan 29, 04:00:05 PM: 6a7ce182 INFO   ---
Jan 29, 04:00:05 PM: 6a7ce182 INFO   RUNNING SCHEDULED FUNCTION:
Jan 29, 04:00:05 PM: 6a7ce182 INFO   CALLING doPost() FUNCTION:
Jan 29, 04:00:05 PM: 6a7ce182 INFO   NOW ATTEMPTING TO POST: Understood what?
Jan 29, 04:00:05 PM: 6a7ce182 Duration: 4.72 ms	Memory Usage: 107 MB	
Jan 29, 04:00:07 PM: 54bcd4a4 INFO   ---
Jan 29, 04:00:07 PM: 54bcd4a4 INFO   RUNNING SCHEDULED FUNCTION:
Jan 29, 04:00:07 PM: 54bcd4a4 INFO   CALLING doPost() FUNCTION:
Jan 29, 04:00:07 PM: 54bcd4a4 INFO   NOW ATTEMPTING TO POST: Garthie, do you realise what it means?
Jan 29, 04:00:07 PM: 54bcd4a4 Duration: 3.97 ms	Memory Usage: 107 MB	

I have tested the function locally using netlify functions:serve on the command line and it works as expected:

Request from ::ffff:127.0.0.1: GET /.netlify/functions/do-post
---
RUNNING SCHEDULED FUNCTION:
Received event: {
  path: '/.netlify/functions/do-post',
  httpMethod: 'GET',
  queryStringParameters: {},
  multiValueQueryStringParameters: {},
  headers: {
    host: 'localhost:9999',
    'user-agent': 'Netlify Clockwork',
    accept: 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8',
    'accept-language': 'en-US,en;q=0.5',
    'accept-encoding': 'gzip, deflate, br',
    dnt: '1',
    connection: 'keep-alive',
    cookie: 'io=V4kWbrwdCv6BmVz7AAAK; local_adminer_session=24271; adminer_sid=2ae2da35383be1267510b8b3b85a32ba',
    'upgrade-insecure-requests': '1',
    'sec-fetch-dest': 'document',
    'sec-fetch-mode': 'navigate',
    'sec-fetch-site': 'none',
    'sec-fetch-user': '?1',
    'if-none-match': 'W/"128-06BYhxtJ8MRp/hknWZwCKtsq4R8"',
    'client-ip': '127.0.0.1',
    'X-NF-Event': 'schedule'
  },
  multiValueHeaders: {
    host: [ 'localhost:9999' ],
    'user-agent': [
      'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:108.0) Gecko/20100101 Firefox/108.0'
    ],
    accept: [
      'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8'
    ],
    'accept-language': [ 'en-US,en;q=0.5' ],
    'accept-encoding': [ 'gzip, deflate, br' ],
    dnt: [ '1' ],
    connection: [ 'keep-alive' ],
    cookie: [
      'io=V4kWbrwdCv6BmVz7AAAK; local_adminer_session=24271; adminer_sid=2ae2da35383be1267510b8b3b85a32ba'
    ],
    'upgrade-insecure-requests': [ '1' ],
    'sec-fetch-dest': [ 'document' ],
    'sec-fetch-mode': [ 'navigate' ],
    'sec-fetch-site': [ 'none' ],
    'sec-fetch-user': [ '?1' ],
    'if-none-match': [ 'W/"128-06BYhxtJ8MRp/hknWZwCKtsq4R8"' ],
    'client-ip': [ '127.0.0.1' ]
  },
  body: '{"next_run":"2023-01-30T04:00:00.000Z"}',
  isBase64Encoded: false,
  rawUrl: 'http://localhost:9999/.netlify/functions/do-post',
  rawQuery: ''
}
In context: {
  done: [Function: bound ],
  fail: [Function: bound ],
  succeed: [Function: bound ],
  getRemainingTimeInMillis: [Function: bound ],
  callbackWaitsForEmptyEventLoop: false,
  functionName: 'handler',
  functionVersion: '1.0',
  invokedFunctionArn: 'arn:aws:lambda:us-east-1:902923746778:function:handler:1.0',
  memoryLimitInMB: '457',
  awsRequestId: '018bf8a6-0e81-10ea-6d54-ec5124fdf3c7',
  logGroupName: 'Group name',
  logStreamName: 'Stream name',
  identity: null,
  clientContext: {},
  _stopped: false
}
CALLING doPost() FUNCTION:
NOW ATTEMPTING TO POST: Are we not to be friends?
Response with status 304 in 220 ms.
LOGGING IN TO MASTODON: Client {
  http: HttpNativeImpl {
    serializer: SerializerNativeImpl {},
    config: MastoConfig {
      props: [Object],
      serializer: SerializerNativeImpl {}
    },
    logger: LoggerConsoleImpl { logLevel: [LogLevel] }
  },
  ws: WsNativeImpl {
    config: MastoConfig {
      props: [Object],
      serializer: SerializerNativeImpl {}
    },
    serializer: SerializerNativeImpl {},
    logger: LoggerConsoleImpl { logLevel: [LogLevel] }
  },
  config: MastoConfig {
    props: {
      url: 'https://botsin.space',
      accessToken: 'UI-lk2EbIdAiGTkBxAB4p8BNv5eGp9vYEL381e_blwI',
      version: [SemVer2],
      streamingApiUrl: 'wss://botsin.space'
    },
    serializer: SerializerNativeImpl {}
  },
  logger: LoggerConsoleImpl { logLevel: LogLevel { level: 12 } },
  v1: AggregateRepository {
    http: HttpNativeImpl {
      serializer: SerializerNativeImpl {},
      config: [MastoConfig],
      logger: [LoggerConsoleImpl]
    },
    config: MastoConfig {
      props: [Object],
      serializer: SerializerNativeImpl {}
    },
    logger: LoggerConsoleImpl { logLevel: [LogLevel] },
    admin: AggregateRepositoryAdmin {
      http: [HttpNativeImpl],
      config: [MastoConfig],
      logger: [LoggerConsoleImpl],
      accounts: [AccountRepository2],
      canonicalEmailBlocks: [CanonicalEmailBlockRepository],
      dimensions: [DimensionRepository],
      domainBlocks: [DomainBlockRepository2],
      domainAllows: [DomainAllowRepository],
      emailDomainBlocks: [EmailDomainBlockRepository],
      ipBlocks: [IpBlockRepository],
      measures: [MeasureRepository],
      reports: [ReportRepository2],
      retention: [RetentionRepository],
      trends: [TrendRepository2]
    },
    stream: StreamRepository {
      ws: [WsNativeImpl],
      config: [MastoConfig],
      logger: [LoggerConsoleImpl]
    },
    accounts: AccountRepository {
      http: [HttpNativeImpl],
      config: [MastoConfig],
      logger: [LoggerConsoleImpl]
    },
    announcements: AnnouncementRepository {
      http: [HttpNativeImpl],
      config: [MastoConfig],
      logger: [LoggerConsoleImpl]
    },
    apps: AppRepository {
      http: [HttpNativeImpl],
      config: [MastoConfig],
      logger: [LoggerConsoleImpl]
    },
    blocks: BlockRepository {
      http: [HttpNativeImpl],
      config: [MastoConfig],
      logger: [LoggerConsoleImpl]
    },
    bookmarks: BookmarkRepository {
      http: [HttpNativeImpl],
      config: [MastoConfig],
      logger: [LoggerConsoleImpl]
    },
    conversations: ConversationRepository {
      http: [HttpNativeImpl],
      config: [MastoConfig],
      logger: [LoggerConsoleImpl]
    },
    customEmojis: CustomEmojiRepository {
      http: [HttpNativeImpl],
      config: [MastoConfig],
      logger: [LoggerConsoleImpl]
    },
    directory: DirectoryRepository {
      http: [HttpNativeImpl],
      config: [MastoConfig],
      logger: [LoggerConsoleImpl]
    },
    domainBlocks: DomainBlockRepository {
      http: [HttpNativeImpl],
      config: [MastoConfig],
      logger: [LoggerConsoleImpl]
    },
    endorsements: EndorsementRepository {
      http: [HttpNativeImpl],
      config: [MastoConfig],
      logger: [LoggerConsoleImpl]
    },
    favourites: FavouriteRepository {
      http: [HttpNativeImpl],
      config: [MastoConfig],
      logger: [LoggerConsoleImpl]
    },
    featuredTags: FeaturedTagRepository {
      http: [HttpNativeImpl],
      config: [MastoConfig],
      logger: [LoggerConsoleImpl]
    },
    filters: FilterRepository {
      http: [HttpNativeImpl],
      config: [MastoConfig],
      logger: [LoggerConsoleImpl]
    },
    followRequests: FollowRequestRepository {
      http: [HttpNativeImpl],
      config: [MastoConfig],
      logger: [LoggerConsoleImpl]
    },
    instances: InstanceRepository {
      http: [HttpNativeImpl],
      config: [MastoConfig],
      logger: [LoggerConsoleImpl]
    },
    lists: ListRepository {
      http: [HttpNativeImpl],
      config: [MastoConfig],
      logger: [LoggerConsoleImpl]
    },
    markers: MarkerRepository {
      http: [HttpNativeImpl],
      config: [MastoConfig],
      logger: [LoggerConsoleImpl]
    },
    mediaAttachments: MediaAttachmentRepository {
      http: [HttpNativeImpl],
      config: [MastoConfig],
      logger: [LoggerConsoleImpl]
    },
    mutes: MuteRepository {
      http: [HttpNativeImpl],
      config: [MastoConfig],
      logger: [LoggerConsoleImpl]
    },
    notifications: NotificationRepository {
      http: [HttpNativeImpl],
      config: [MastoConfig],
      logger: [LoggerConsoleImpl]
    },
    polls: PollRepository {
      http: [HttpNativeImpl],
      config: [MastoConfig],
      logger: [LoggerConsoleImpl]
    },
    preferences: PreferenceRepository {
      http: [HttpNativeImpl],
      config: [MastoConfig],
      logger: [LoggerConsoleImpl]
    },
    webPushSubscriptions: WebPushSubscriptionRepository {
      http: [HttpNativeImpl],
      config: [MastoConfig],
      logger: [LoggerConsoleImpl]
    },
    reports: ReportRepository {
      http: [HttpNativeImpl],
      config: [MastoConfig],
      logger: [LoggerConsoleImpl]
    },
    scheduledStatuses: ScheduledStatusRepository {
      http: [HttpNativeImpl],
      config: [MastoConfig],
      logger: [LoggerConsoleImpl]
    },
    statuses: StatusRepository {
      http: [HttpNativeImpl],
      config: [MastoConfig],
      logger: [LoggerConsoleImpl]
    },
    suggestions: SuggestionRepository {
      http: [HttpNativeImpl],
      config: [MastoConfig],
      logger: [LoggerConsoleImpl]
    },
    timelines: TimelineRepository {
      http: [HttpNativeImpl],
      config: [MastoConfig],
      logger: [LoggerConsoleImpl]
    },
    trends: TrendRepository {
      http: [HttpNativeImpl],
      config: [MastoConfig],
      logger: [LoggerConsoleImpl]
    },
    email: EmailRepository {
      http: [HttpNativeImpl],
      config: [MastoConfig],
      logger: [LoggerConsoleImpl]
    },
    tags: TagRepository {
      http: [HttpNativeImpl],
      config: [MastoConfig],
      logger: [LoggerConsoleImpl]
    },
    followedTags: FollowedTagRepository {
      http: [HttpNativeImpl],
      config: [MastoConfig],
      logger: [LoggerConsoleImpl]
    }
  },
  v2: AggregateRepository2 {
    http: HttpNativeImpl {
      serializer: SerializerNativeImpl {},
      config: [MastoConfig],
      logger: [LoggerConsoleImpl]
    },
    config: MastoConfig {
      props: [Object],
      serializer: SerializerNativeImpl {}
    },
    logger: LoggerConsoleImpl { logLevel: [LogLevel] },
    filters: FilterRepository2 {
      http: [HttpNativeImpl],
      config: [MastoConfig],
      logger: [LoggerConsoleImpl]
    },
    instance: InstanceRepository2 {
      http: [HttpNativeImpl],
      config: [MastoConfig],
      logger: [LoggerConsoleImpl]
    },
    mediaAttachments: MediaAttachmentRepository2 {
      http: [HttpNativeImpl],
      config: [MastoConfig],
      logger: [LoggerConsoleImpl],
      v1: [MediaAttachmentRepository]
    },
    suggestions: SuggestionRepository2 {
      http: [HttpNativeImpl],
      config: [MastoConfig],
      logger: [LoggerConsoleImpl]
    }
  },
  oauth: OAuthRepository {
    http: HttpNativeImpl {
      serializer: SerializerNativeImpl {},
      config: [MastoConfig],
      logger: [LoggerConsoleImpl]
    }
  }
}
RESULT OF ATTEMPT TO POST: {
  id: '109775359056840661',
  createdAt: '2023-01-30T00:37:28.113Z',
  inReplyToId: null,
  inReplyToAccountId: null,
  sensitive: false,
  spoilerText: '',
  visibility: 'public',
  language: 'en',
  uri: 'https://botsin.space/users/obliquestions/statuses/109775359056840661',
  url: 'https://botsin.space/@obliquestions/109775359056840661',
  repliesCount: 0,
  reblogsCount: 0,
  favouritesCount: 0,
  editedAt: null,
  favourited: false,
  reblogged: false,
  muted: false,
  bookmarked: false,
  pinned: false,
  content: '<p>Are we not to be friends?</p>',
  filtered: [],
  reblog: null,
  application: {
    name: 'Oblique Questions',
    website: 'https://github.com/matthewmcvickar/oblique-questions-bot'
  },
  account: {
    id: '86708',
    username: 'obliquestions',
    acct: 'obliquestions',
    displayName: 'Oblique Questions',
    locked: false,
    bot: true,
    discoverable: null,
    group: false,
    createdAt: '2018-11-14T00:00:00.000Z',
    note: '<p>Questions without context, extracted from Project Gutenberg books.</p>',
    url: 'https://botsin.space/@obliquestions',
    avatar: 'https://files.botsin.space/accounts/avatars/000/086/708/original/4f7fd3c42f0938c8.png',
    avatarStatic: 'https://files.botsin.space/accounts/avatars/000/086/708/original/4f7fd3c42f0938c8.png',
    header: 'https://files.botsin.space/accounts/headers/000/086/708/original/7643860d3ae67d2d.png',
    headerStatic: 'https://files.botsin.space/accounts/headers/000/086/708/original/7643860d3ae67d2d.png',
    followersCount: 59,
    followingCount: 2,
    statusesCount: 7870,
    lastStatusAt: '2023-01-30',
    noindex: true,
    emojis: [],
    fields: [ [Object], [Object], [Object] ]
  },
  mediaAttachments: [],
  mentions: [],
  tags: [],
  emojis: [],
  card: null,
  poll: null
}
SUCCESSFULLY POSTED TO MASTODON:  https://botsin.space/@obliquestions/109775359056840661

Any ideas?

I figured out from another thread that it was firing three times because I wasn’t doing return { statusCode: 200 } in the handler function.

It’s still not doing the post-to-Mastodon part, but I think that might be due to a timeout when trying to connect to Mastodon. I don’t know why it’s only happening on Netlify and not locally, though.

Hi @matthewmcvickar thank you for coming back and sharing your solution. I’m glad you got it sorted.

The function is working fully now and posting to Mastodon. Logs showed it was timing out when trying to connect to the server, so I may have been hitting a rate limit for requests coming from Netlify. In any case, it’s all working now!

yay! thanks so much for confirming matthew. (: