Summary
Deploying an application from a git URL onto a default-configured instance fails with a raw
Node ENOENT naming an internal server temp path when the repository has no package.json
at its root. Studio shows that string verbatim, so the user is told:
Failed to import <project>
ENOENT: no such file or directory, open '/tmp/harper-git-clone-oXPS7c/package.json'
Nothing in that names the actual problem ("this repository has no package.json in its root"),
and the path it does name belongs to a directory on the Harper node that the user cannot see
and that is deleted moments later.
In the last 30 days this is 19 events across 4 sessions, and it is the single largest
SSEOperationError family (19 of 37 events). Two of the affected sessions reached the import
form from /finish-setup minutes earlier — i.e. this is happening during new-cluster onboarding.
Surfaced by the automated daily RUM review (app f590deee-…).
Datadog findings
@type:error @error.message:*harper-git-clone* — 30d → now:
|
|
| Events |
19 |
| Sessions |
4 |
| View |
…/apps in every case |
error.source / handling |
console / handled |
| Error type |
SSEOperationError |
Retry behaviour per session — the users did not read this as "fix your repository", they read it
as "it broke, try again":
| Session |
Attempts |
Span |
What happened next |
| A |
9 |
22:08 → 22:30 (22 min) |
left /apps, browsed cluster + domains, no further deploy |
| B |
8 |
03:13 → 03:19 (6 min) |
arrived from /finish-setup 8 min earlier; gave up |
| C |
1 |
— |
arrived from /finish-setup 6 min earlier |
| D |
1 |
— |
session ends |
Session B's first three attempts were a different failure (a malformed URL — Harper's message was
at least legible there), then it switched to a well-formed URL and hit this one 8 times.
Wider SSEOperationError context, same 30d window: 37 events / 5 sessions. Every session in
RUM that attempted a git deploy in the window failed at least once, and none shows a subsequent
success in the same session. Note the denominator limitation: a successful deploy emits no RUM
error, and the deploy travels over SSE to the shared /operation endpoint, so RUM cannot tell a
deploy request apart from any other operation. This is "5 sessions failed", not "5 of N".
Root cause (server), confirmed
harper components/Application.ts:498-499 (verified at 724c317d8, v5.2.4-162):
const manifestPath = join(cloneDir, 'package.json');
const manifest = JSON.parse(await readFile(manifestPath, 'utf8'));
readFile is unguarded. When the clone has no root package.json, Node throws exactly the
observed string — reproduced locally:
ENOENT: no such file or directory, open '/…/harper-git-clone-o0nMZ5/package.json'
This is on the default path: packGitReferenceWithoutScripts runs for any git reference
whenever install scripts are disallowed (Application.ts:832-833), which is the default.
The real fix belongs here — a stat/existsSync guard throwing something like
"<repo> has no package.json in its root; a Harper application needs one". That is a one-guard
change in HarperFast/harper, and it would fix this for every client, not just Studio.
Studio's side of it
Studio renders the server string verbatim in two places, and only one of them does any
failure-to-guidance mapping:
src/features/instance/applications/components/NewApplication/useImportApplication.ts:104 —
the new-application import toast: description: error instanceof Error ? error.message : undefined.
This is the surface all 19 events hit. It is a toast, so the text fades.
src/features/instance/applications/modals/RedeployApplicationModal.tsx:108 →
DeployProgress.tsx renders {state.error} in the failure block.
DeployProgress.tsx already establishes the pattern for exactly this problem — SSH_ACCESS_FAILURE
matches a known Harper failure string and adds an actionable "Manage SSH keys" link, with a comment
explaining why the mapping is deliberate. There is no equivalent for the missing-manifest failure,
and the import toast (surface 1) has no mapping at all.
Suggested fix
Preference order:
- Guard the read in
HarperFast/harper (components/Application.ts:499) and throw a
descriptive error. Everything downstream then improves for free. Worth filing there.
- In Studio, in the meantime, recognise the missing-manifest failure at both surfaces and
render guidance instead of the raw string — following the SSH_ACCESS_FAILURE precedent, and
suppressing the internal /tmp/harper-git-clone-… path either way. Doing surface 1 only would
repeat the asymmetry the existing SSH mapping already has.
- Independently: the import failure is a fading toast for a failure the user has to act on
(edit a repository, then retry). Session A retried 9 times over 22 minutes. Consider giving the
import form the same persistent failure panel DeployProgress uses for redeploy.
Notes / limitations
- No customer identifiers, e-mails, repository names, org/cluster/instance IDs, or session IDs are
included here. Session labels A–D are local to this issue.
- Timestamps are UTC.
- Redaction status: these events pre-date
redactRelayedMessage reaching production. That shipped
in v2.169.5 (commit 0d407cf1, 2026-08-31); production ran v2.163.1 and v2.169.3 for the
whole sample and only moved to v2.169.13 in the last day. Going forward the server text is
withheld from Error Tracking — which means this family will stop being measurable in RUM, and
is another reason to fix it now rather than watch it.
Summary
Deploying an application from a git URL onto a default-configured instance fails with a raw
Node
ENOENTnaming an internal server temp path when the repository has nopackage.jsonat its root. Studio shows that string verbatim, so the user is told:
Nothing in that names the actual problem ("this repository has no
package.jsonin its root"),and the path it does name belongs to a directory on the Harper node that the user cannot see
and that is deleted moments later.
In the last 30 days this is 19 events across 4 sessions, and it is the single largest
SSEOperationErrorfamily (19 of 37 events). Two of the affected sessions reached the importform from
/finish-setupminutes earlier — i.e. this is happening during new-cluster onboarding.Surfaced by the automated daily RUM review (app
f590deee-…).Datadog findings
@type:error @error.message:*harper-git-clone*— 30d → now:…/appsin every caseerror.source/handlingconsole/handledSSEOperationErrorRetry behaviour per session — the users did not read this as "fix your repository", they read it
as "it broke, try again":
/apps, browsed cluster + domains, no further deploy/finish-setup8 min earlier; gave up/finish-setup6 min earlierSession B's first three attempts were a different failure (a malformed URL — Harper's message was
at least legible there), then it switched to a well-formed URL and hit this one 8 times.
Wider
SSEOperationErrorcontext, same 30d window: 37 events / 5 sessions. Every session inRUM that attempted a git deploy in the window failed at least once, and none shows a subsequent
success in the same session. Note the denominator limitation: a successful deploy emits no RUM
error, and the deploy travels over SSE to the shared
/operationendpoint, so RUM cannot tell adeploy request apart from any other operation. This is "5 sessions failed", not "5 of N".
Root cause (server), confirmed
harpercomponents/Application.ts:498-499(verified at724c317d8,v5.2.4-162):readFileis unguarded. When the clone has no rootpackage.json, Node throws exactly theobserved string — reproduced locally:
This is on the default path:
packGitReferenceWithoutScriptsruns for any git referencewhenever install scripts are disallowed (
Application.ts:832-833), which is the default.The real fix belongs here — a
stat/existsSyncguard throwing something like"
<repo>has no package.json in its root; a Harper application needs one". That is a one-guardchange in
HarperFast/harper, and it would fix this for every client, not just Studio.Studio's side of it
Studio renders the server string verbatim in two places, and only one of them does any
failure-to-guidance mapping:
src/features/instance/applications/components/NewApplication/useImportApplication.ts:104—the new-application import toast:
description: error instanceof Error ? error.message : undefined.This is the surface all 19 events hit. It is a toast, so the text fades.
src/features/instance/applications/modals/RedeployApplicationModal.tsx:108→DeployProgress.tsxrenders{state.error}in the failure block.DeployProgress.tsxalready establishes the pattern for exactly this problem —SSH_ACCESS_FAILUREmatches a known Harper failure string and adds an actionable "Manage SSH keys" link, with a comment
explaining why the mapping is deliberate. There is no equivalent for the missing-manifest failure,
and the import toast (surface 1) has no mapping at all.
Suggested fix
Preference order:
HarperFast/harper(components/Application.ts:499) and throw adescriptive error. Everything downstream then improves for free. Worth filing there.
render guidance instead of the raw string — following the
SSH_ACCESS_FAILUREprecedent, andsuppressing the internal
/tmp/harper-git-clone-…path either way. Doing surface 1 only wouldrepeat the asymmetry the existing SSH mapping already has.
(edit a repository, then retry). Session A retried 9 times over 22 minutes. Consider giving the
import form the same persistent failure panel
DeployProgressuses for redeploy.Notes / limitations
included here. Session labels A–D are local to this issue.
redactRelayedMessagereaching production. That shippedin
v2.169.5(commit0d407cf1, 2026-08-31); production ranv2.163.1andv2.169.3for thewhole sample and only moved to
v2.169.13in the last day. Going forward the server text iswithheld from Error Tracking — which means this family will stop being measurable in RUM, and
is another reason to fix it now rather than watch it.