From a3ec068f943d3770e6f2492a5aa9cd3fb0a41e33 Mon Sep 17 00:00:00 2001 From: Kris Zyp Date: Fri, 22 May 2026 09:27:18 -0600 Subject: [PATCH 1/2] Detect SSH auth failure on private-repo deploy and emit clear error When npm pack fails with git exit 128 due to SSH auth issues (missing key, publickey denied, host key unverified, or missing SSH uid), translate the raw npm/git stderr passthrough into a single actionable error message that tells the user to configure their SSH key. Original stderr is preserved via the error cause chain. Closes #185 Co-Authored-By: Claude Sonnet 4.6 --- components/Application.ts | 21 ++++++++ unitTests/components/Application.test.js | 67 ++++++++++++++++++++++++ 2 files changed, 88 insertions(+) create mode 100644 unitTests/components/Application.test.js diff --git a/components/Application.ts b/components/Application.ts index 15d9c99d4..80ae2d0d0 100644 --- a/components/Application.ts +++ b/components/Application.ts @@ -108,6 +108,21 @@ export function assertApplicationConfig( } } +/** + * Returns true when npm/git stderr indicates an SSH authentication failure — + * git exits 128 with the standard "could not read" message, or the SSH layer + * reports a missing uid (no SSH daemon user), explicit publickey denial, or + * an unverified host key. + */ +export function isSSHAuthFailure(stderr: string): boolean { + return ( + stderr.includes('Could not read from remote repository') || + stderr.includes('Permission denied (publickey)') || + stderr.includes('No user exists for uid') || + stderr.includes('Host key verification failed') + ); +} + /** * Extract an application given payload (content of the application) or package (npm-compatible identifier to the application). * @@ -189,6 +204,12 @@ export async function extractApplication(application: Application) { parentDirPath ); if (code !== 0) { + if (isSSHAuthFailure(stderr)) { + throw new Error( + `Failed to deploy private repository ${application.packageIdentifier}: SSH authentication failed. Verify the repository URL, configure an SSH key on this Harper instance, and ensure it has access to the target repository.`, + { cause: new Error(stderr) } + ); + } throw new Error(`Failed to download package ${application.packageIdentifier}: ${stderr}`); } diff --git a/unitTests/components/Application.test.js b/unitTests/components/Application.test.js new file mode 100644 index 000000000..fdc855d62 --- /dev/null +++ b/unitTests/components/Application.test.js @@ -0,0 +1,67 @@ +'use strict'; + +const assert = require('node:assert'); + +const testUtils = require('../testUtils.js'); +testUtils.preTestPrep(); + +const { isSSHAuthFailure } = require('#src/components/Application'); + +describe('isSSHAuthFailure', () => { + it('returns true for "Could not read from remote repository"', () => { + const stderr = ` +npm error code 128 +npm error An unknown git error occurred +npm error command git --no-replace-objects ls-remote git@github.com:Org/repo.git +npm error fatal: Could not read from remote repository. +npm error Please make sure you have the correct access rights +npm error and the repository exists. +`; + assert.strictEqual(isSSHAuthFailure(stderr), true); + }); + + it('returns true for "Permission denied (publickey)"', () => { + const stderr = ` +npm error code 128 +npm error An unknown git error occurred +npm error git@github.com: Permission denied (publickey). +npm error fatal: Could not read from remote repository. +`; + assert.strictEqual(isSSHAuthFailure(stderr), true); + }); + + it('returns true for "No user exists for uid"', () => { + const stderr = ` +npm error code 128 +npm error An unknown git error occurred +npm error No user exists for uid 42932 +npm error fatal: Could not read from remote repository. +npm error Please make sure you have the correct access rights +npm error and the repository exists. +`; + assert.strictEqual(isSSHAuthFailure(stderr), true); + }); + + it('returns true for "Host key verification failed"', () => { + const stderr = ` +npm error code 128 +npm error An unknown git error occurred +npm error Host key verification failed. +npm error fatal: Could not read from remote repository. +`; + assert.strictEqual(isSSHAuthFailure(stderr), true); + }); + + it('returns false for unrelated npm errors', () => { + const stderr = ` +npm error code E404 +npm error 404 Not Found - GET https://registry.npmjs.org/nonexistent-pkg +npm error 404 '@scope/nonexistent-pkg@latest' is not in this registry. +`; + assert.strictEqual(isSSHAuthFailure(stderr), false); + }); + + it('returns false for empty stderr', () => { + assert.strictEqual(isSSHAuthFailure(''), false); + }); +}); From 3245d7001dca2a73722eedad4bda3b8ba5afa7f0 Mon Sep 17 00:00:00 2001 From: Kris Zyp Date: Fri, 22 May 2026 09:29:42 -0600 Subject: [PATCH 2/2] fix: include known_hosts guidance in SSH deploy error message The prior message only mentioned SSH key configuration, which would mislead users hitting a host-key verification failure (the newly added Host key verification failed pattern). Broaden to also point at ssh/known_hosts so all four detected failure modes are covered. Co-Authored-By: Claude Sonnet 4.6 --- components/Application.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/Application.ts b/components/Application.ts index 80ae2d0d0..a6d044aaa 100644 --- a/components/Application.ts +++ b/components/Application.ts @@ -206,7 +206,7 @@ export async function extractApplication(application: Application) { if (code !== 0) { if (isSSHAuthFailure(stderr)) { throw new Error( - `Failed to deploy private repository ${application.packageIdentifier}: SSH authentication failed. Verify the repository URL, configure an SSH key on this Harper instance, and ensure it has access to the target repository.`, + `Failed to deploy private repository ${application.packageIdentifier}: SSH access failed. Verify the repository URL, configure an SSH key on this Harper instance, ensure the key has access to the target repository, and confirm the host is present in the ssh/known_hosts file.`, { cause: new Error(stderr) } ); }