Summary
.github/actions/archive_on_merge/archive-on-merge.js is authored as CommonJS (require("child_process"), require("fs"), module.exports, require.main === module). It ships with no package.json of its own inside .github/actions/archive_on_merge/, and the reusable workflow (archive-on-merge.yml) checks it out via sparse-checkout into the consumer repo's own workspace, then runs it with plain node.
Node decides whether a .js file is CommonJS or ESM by walking up the directory tree from that file's location, looking for the nearest package.json and reading its "type" field. Since nothing sits between the checked-out script and the consumer repo's root, that walk finds the consumer's own root package.json — not anything belonging to shared-actions.
If the consumer repo's package.json contains "type": "module" (increasingly common — ESM-first Node projects, mcp/tooling packages, etc.), Node misreads the checked-out CommonJS script as an ES module, and it fails immediately:
ReferenceError: require is not defined in ES module scope, you can use import instead
This file is being treated as an ES module because it has a '.js' file extension and
'<consumer-repo>/package.json' contains "type": "module".
This is not a consumer-repo misconfiguration — declaring "type": "module" is completely standard, and the consumer repo has no reason to expect it will affect a third-party script sparse-checked-out into a subdirectory of its own workspace.
Why this doesn't show up today
It only manifests in a consumer repo whose root package.json sets "type": "module". A consumer whose package.json omits "type" (defaulting to CommonJS) — or has no root package.json at all — never triggers it, because the walk-up resolution lands on a CommonJS-compatible context by coincidence, not because anything in this workflow accounted for it.
That means the current safety of any given consumer is incidental to that repo's own module convention, not a property of this workflow. The same consumer would start failing the moment it adopts "type": "module" — a routine, increasingly common migration — with no change on this workflow's side.
Reproduction
- A repo with a root
package.json containing "type": "module" adopts the caller pattern from this repo's README (uses: deriv-com/shared-actions/.github/workflows/archive-on-merge.yml@master).
- Merge any PR into that repo's base branch.
- The
Detect + archive completed changes step fails with the ReferenceError above, regardless of whether there is actually an OpenSpec change to archive — the crash happens before main() runs at all.
Suggested fix
Either of these removes the dependency on the consumer's module-type ambient resolution entirely:
- Rename
archive-on-merge.js → archive-on-merge.cjs, and update the run: line in archive-on-merge.yml to match. The .cjs extension is always treated as CommonJS by Node regardless of any ancestor package.json — simplest fix, no logic changes.
- Or add a
package.json (with no "type" field, or explicit "type": "commonjs") inside .github/actions/archive_on_merge/, so Node's walk-up stops there instead of continuing into the consumer's tree.
The .cjs rename is the smaller, more explicit change and doesn't rely on Node's directory-walk semantics being understood correctly by future maintainers.
Impact if unaddressed
Any consumer repo that is or becomes "type": "module" gets a hard failure on this workflow's core step, every time it runs, with no OpenSpec-side workaround available from the caller side — the reusable workflow's inputs: (base_branch, node_version, openspec_version, action_ref) offer no way to override module resolution or insert a step between the checkout and the run: line.
Happy to open a PR with the .cjs rename if that's the preferred fix — flagging first since I don't have write access to this repo.
Summary
.github/actions/archive_on_merge/archive-on-merge.jsis authored as CommonJS (require("child_process"),require("fs"),module.exports,require.main === module). It ships with nopackage.jsonof its own inside.github/actions/archive_on_merge/, and the reusable workflow (archive-on-merge.yml) checks it out via sparse-checkout into the consumer repo's own workspace, then runs it with plainnode.Node decides whether a
.jsfile is CommonJS or ESM by walking up the directory tree from that file's location, looking for the nearestpackage.jsonand reading its"type"field. Since nothing sits between the checked-out script and the consumer repo's root, that walk finds the consumer's own rootpackage.json— not anything belonging toshared-actions.If the consumer repo's
package.jsoncontains"type": "module"(increasingly common — ESM-first Node projects,mcp/tooling packages, etc.), Node misreads the checked-out CommonJS script as an ES module, and it fails immediately:This is not a consumer-repo misconfiguration — declaring
"type": "module"is completely standard, and the consumer repo has no reason to expect it will affect a third-party script sparse-checked-out into a subdirectory of its own workspace.Why this doesn't show up today
It only manifests in a consumer repo whose root
package.jsonsets"type": "module". A consumer whosepackage.jsonomits"type"(defaulting to CommonJS) — or has no rootpackage.jsonat all — never triggers it, because the walk-up resolution lands on a CommonJS-compatible context by coincidence, not because anything in this workflow accounted for it.That means the current safety of any given consumer is incidental to that repo's own module convention, not a property of this workflow. The same consumer would start failing the moment it adopts
"type": "module"— a routine, increasingly common migration — with no change on this workflow's side.Reproduction
package.jsoncontaining"type": "module"adopts the caller pattern from this repo's README (uses: deriv-com/shared-actions/.github/workflows/archive-on-merge.yml@master).Detect + archive completed changesstep fails with theReferenceErrorabove, regardless of whether there is actually an OpenSpec change to archive — the crash happens beforemain()runs at all.Suggested fix
Either of these removes the dependency on the consumer's module-type ambient resolution entirely:
archive-on-merge.js→archive-on-merge.cjs, and update therun:line inarchive-on-merge.ymlto match. The.cjsextension is always treated as CommonJS by Node regardless of any ancestorpackage.json— simplest fix, no logic changes.package.json(with no"type"field, or explicit"type": "commonjs") inside.github/actions/archive_on_merge/, so Node's walk-up stops there instead of continuing into the consumer's tree.The
.cjsrename is the smaller, more explicit change and doesn't rely on Node's directory-walk semantics being understood correctly by future maintainers.Impact if unaddressed
Any consumer repo that is or becomes
"type": "module"gets a hard failure on this workflow's core step, every time it runs, with no OpenSpec-side workaround available from the caller side — the reusable workflow'sinputs:(base_branch,node_version,openspec_version,action_ref) offer no way to override module resolution or insert a step between the checkout and therun:line.Happy to open a PR with the
.cjsrename if that's the preferred fix — flagging first since I don't have write access to this repo.