HarperFast / HarperFast/studio
[RUM] Git deploy with no root package.json shows a raw ENOENT naming a server temp path — 19 events, 4 sessions, 2 mid-onboarding
- Dominant language
- TypeScript
- Stars
- 5
- Forks
- 4
- Avg merge
- 1d 8h
- Merged PRs (30d)
- 40
Description
## 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 ``**
> `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`):
```ts
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
*"`` 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:
1. `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.
2. `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:
1. **Guard the read in `HarperFast/harper`** (`components/Application.ts:499`) and throw a
descriptive error. Everything downstream then improves for free. Worth filing there.
2. **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.
3. 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.
Contributor guide
Research direction
Start with src/features/instance/applications/components/NewApplication/useImportApplication.ts and the DeployProgress.tsx failure handling, then inspect RedeployApplicationModal.tsx. Reproduce or trace a missing-root-package.json failure and compare both surfaces. Done means both Studio paths provide actionable missing-manifest guidance without exposing the internal temporary path.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- typescript
- Domain
- frontend
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 70/100