can1357/oh-my-pi · error · LegacyDestinationError

credentials ${usernameKey} and ${passwordKey} must be config

Error message

credentials ${usernameKey} and ${passwordKey} must be configured together

What it means

This LegacyDestinationError is thrown by optionalBasicHeaders() when exactly one of a destination's paired basic-auth credentials is configured (username without password, or vice versa). Since the two are combined into a single HTTP Basic Authorization header, a half-configured pair can never authenticate, so the library fails fast with a message naming both config keys.

Source

Thrown at packages/coding-agent/src/blob-broker/uploaders-legacy.ts:159

	if (!raw) throw new LegacyDestinationError(destination, "the upload response did not include a direct image URL");
	return httpUrl(destination, raw, base);
}

function basicAuthorization(username: string, password: string): string {
	return `Basic ${Buffer.from(`${username}:${password}`, "utf8").toString("base64")}`;
}

function optionalBasicHeaders(
	destination: BlobDestinationId,
	config: DestinationRuntimeConfig,
	usernameKey: string,
	passwordKey: string,
): Headers | undefined {
	const username = credentialString(config, usernameKey);
	const password = credentialString(config, passwordKey);
	if (!username && !password) return undefined;
	if (!username || !password) {
		throw new LegacyDestinationError(
			destination,
			`credentials ${usernameKey} and ${passwordKey} must be configured together`,
		);
	}
	return new Headers({ Authorization: basicAuthorization(username, password) });
}

function xmlEntityDecode(value: string): string {
	return value
		.replaceAll(""", '"')
		.replaceAll("'", "'")
		.replaceAll("&lt;", "<")
		.replaceAll("&gt;", ">")
		.replaceAll("&amp;", "&");
}

function xmlAttribute(source: string, element: string, attribute: string): string | undefined {
	const elementMatch = source.match(new RegExp(`<${element}\\b[^>]*>`, "i"));

View on GitHub (pinned to 9690622007)

Solutions

  1. Set both credential keys in the destination config (the two key names are given verbatim in the error message)
  2. If the endpoint needs no auth, clear BOTH keys so optionalBasicHeaders() returns undefined instead of throwing
  3. Check environment/secret injection so both values resolve — one may be empty string or missing
  4. Re-run with both values present and verify the Authorization header is sent

Example fix

// before
{ basicUsername: "alice" }
// after
{ basicUsername: "alice", basicPassword: "secret" }
Defensive patterns

Strategy: validation

Validate before calling

const u = config.basicUsername, p = config.basicPassword;
if ((u && !p) || (!u && p)) {
  throw new Error("basicUsername and basicPassword must be configured together");
}

Type guard

function hasCompleteBasicAuth(c: { basicUsername?: string; basicPassword?: string }): boolean {
  return (!!c.basicUsername && !!c.basicPassword) || (!c.basicUsername && !c.basicPassword);
}

Try / catch

try {
  await uploader.upload(request);
} catch (err) {
  if (err instanceof Error && err.message.includes("must be configured together")) {
    // set or clear both credential keys named in the message
  } else throw err;
}

Prevention

When it happens

Trigger: Setting only the username key (e.g. for transfer-sh: only `username`/token without `password`, or lobfile/localhostr pairs) in DestinationRuntimeConfig; the message template embeds the actual key names, e.g. 'credentials basicUsername and basicPassword must be configured together'; thrown while building request headers in createLegacyUploader -> headers.

Common situations: Users fill in a username during setup and skip the password assuming it is optional; secrets managers injecting only one of the two variables; rotating credentials and removing one value; copy-pasting a config example with only one field.

Related errors


AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31). Data as JSON: /api/errors/8a5393542e2a9a31. Report an issue: GitHub.