Skip to content

Payload-deployed components are never reinstalled, so a broken install cannot recover #1974

Description

@heskew

Summary

A component deployed by payload gets exactly one dependency install — at deploy time — and never again. If that install fails or its node_modules is lost, nothing restores it. Restarts don't help, because boot-time installation never considers the component at all.

Observed on 5.1.x: a component's node_modules was destroyed by a failed install (see #1973). Every subsequent restart left it broken with Cannot find module at resource load, and no npm run was even attempted — the npm logs were unchanged across restarts.

Why

deployComponent (components/operations.js) only writes an application entry to the root config when the request carries a package identifier:

if (req.package) {
    const applicationConfig = { package: req.package };
    ...
    await configUtils.addConfig(req.project, applicationConfig);
}

A payload deploy has no req.package, so no entry is written. installApplications() iterates root-config entries that have a package property, so a payload-deployed component is never in that set — it isn't in harper-application-lock.json either. It's loaded from the components directory on every boot, but its dependencies are never verified or reinstalled.

So the install is effectively one-shot, with no integrity check and no recovery path, for a whole class of deploys.

Possibly related, unverified

Even for root-config applications, installApplication early-returns on mere existence of the directory:

await access(join(application.dirPath, 'node_modules'), constants.F_OK);
// -> "already has node_modules; skipping install"

Separately, something in boot creates node_modules/harper (a symlink to the installed Harper package) so components can import harper. In the case observed, node_modules was absent before restart and afterwards contained only that symlink.

If that symlink is created before the existence check runs, then an empty-but-present node_modules would satisfy the check and suppress a needed install. I could not confirm this, because for the component in question installApplication was never reached at all (per the root-config gap above). Flagging it as a hazard worth checking rather than a diagnosis — if real, it affects root-config apps too, and an existence check is the wrong test either way.

Suggested direction

Make dependency state verifiable and repairable independent of how the component was deployed: record what was installed, check it at load, and reinstall when it doesn't match — rather than inferring "installed" from a directory existing. At minimum, payload-deployed components should be reachable by the same boot-time install path as package-identifier ones.

Related: #1973 (how the install gets destroyed), #1975 (why the breakage stays invisible).


🤖 Filed by Claude on behalf of @heskew

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:componentsComponents / applications subsystembugSomething isn't working

    Type

    Fields

    Priority

    P1

    Projects

    No projects

      Milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions