Skip to content

resultChanged() crashes on undefined result and permanently prevents triggers from firing after transient registry errors #923

@dmarmor

Description

@dmarmor

Summary

When a transient registry error (502, socket hang up, etc.) occurs during findNewVersion() on the first check after restart, the container's result is left as undefined. This causes resultChanged() to throw TypeError: Cannot read properties of undefined (reading 'tag'), which prevents mapContainerToContainerReport() from producing a valid report. As a consequence, triggers never fire for that container's update — even after subsequent watch cycles successfully detect the update.

The only recovery is restarting WUD.

Environment

  • WUD version: 8.1.1
  • Trigger types: command (with WUD_TRIGGER_COMMAND_<commandname>_ONCE=true) and SMTP

Sequence of Events

  1. WUD watches a container for the first time (or after restart).
  2. findNewVersion() fails with a transient error (e.g., registry returns 502) on the first check.
  3. Container is inserted into the store with result = undefined.
  4. mapContainerToContainerReport sets changed = true (first-time path), but updateAvailable is false (because result is undefined), so the trigger does not fire (which is to be expected at this point).
  5. On the next watch cycle, findNewVersion() succeeds. Now result is populated and updateAvailable is true.
  6. containerInDb.resultChanged(containerReport.container) is called.
  7. containerInDb was saved in step 3 without a result, so this.result is undefined.
  8. this.result.tag throws TypeError: Cannot read properties of undefined (reading 'tag').
  9. The exception propagates, changed is never set to true, and the trigger never fires.
  10. The store is then updated with the valid result, so on the next cycle resultChanged() sees no change, so changed = false and the trigger still doesn't fire.
  11. The container shows as "update available" on the dashboard, but no trigger ever runs for it.

Observed Behavior

On my system, when the transient error occurred, the WUD log showed:

11:01:07  WARN whats-up-docker/watcher.docker.local: Error when processing (502 - "<html><body><h1>502 Bad Gateway</h1>...") (container=local_vikunja)

Then an hour later when the cron caused the system to watch again, I got:

12:00:11.611  INFO whats-up-docker/watcher.docker.local: Cron started (0 * * * *)
12:00:12.162  WARN whats-up-docker/watcher.docker.local: Error when processing some containers (Cannot read properties of undefined (reading 'tag'))

After that, the vikunja container showed an update available on the dashboard, but none of the triggers I had associated with it ever fired. Manually triggering from the dashboard worked fine.

Root Cause

In app/model/container.js, resultChanged does not guard against this.result or otherContainer.result being undefined:

function resultChangedFunction(otherContainer) {
    return (
        otherContainer === undefined ||
        this.result.tag !== otherContainer.result.tag ||     // crashes if this.result is undefined
        this.result.digest !== otherContainer.result.digest ||
        this.result.created !== otherContainer.result.created
    );
}

In app/watchers/providers/docker/Docker.js, watchContainer() deletes result at the start of each cycle and only sets it on success:

delete containerWithResult.result;    // result is now undefined
try {
    containerWithResult.result = await this.findNewVersion(container, logContainer);
} catch (e) {
    containerWithResult.error = { message: e.message };
    // result stays undefined
}

The container is then saved to the store without a result, and subsequent calls to resultChanged() crash.

Suggested Fix

Add null guards in resultChanged():

function resultChangedFunction(otherContainer) {
    if (otherContainer === undefined) return true;
    if (!this.result || !otherContainer.result) return true;  // treat missing result as changed
    return (
        this.result.tag !== otherContainer.result.tag ||
        this.result.digest !== otherContainer.result.digest ||
        this.result.created !== otherContainer.result.created
    );
}

When either side has no result, the function should return true (changed) so that triggers can fire once a valid result appears.

Related Issues

This appears to be the root cause behind multiple open issues reporting the same error:

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions