slopus/happy · error · Error
request-download returned no downloadUrl
Error message
request-download returned no downloadUrl
What it means
Thrown by downloadAttachment when the request-download response does not contain a string downloadUrl. The client requires a pre-signed/pre-authenticated URL to fetch the attachment; its absence means the server refused or failed to generate one.
Source
Thrown at packages/happy-cli/src/api/apiSession.ts:501
/**
* Download an encrypted attachment blob via the request-download flow:
* POST /request-download → { downloadUrl } → GET downloadUrl. Local mode
* downloadUrl points back at our server (Bearer required); S3 mode is a
* presigned URL that does not accept extra headers.
*/
async downloadAttachment(ref: string): Promise<Uint8Array> {
const requestUrl = `${configuration.serverUrl}/v1/sessions/${this.sessionId}/attachments/request-download`;
const requestRes = await axios.post(
requestUrl,
{ ref },
{
headers: { 'Authorization': `Bearer ${this.token}`, 'Content-Type': 'application/json' },
timeout: 30000,
},
);
const downloadUrl = requestRes.data?.downloadUrl;
if (typeof downloadUrl !== 'string') {
throw new Error('request-download returned no downloadUrl');
}
const isServerUrl = downloadUrl.startsWith(configuration.serverUrl);
const headers: Record<string, string> = {};
if (isServerUrl) {
headers['Authorization'] = `Bearer ${this.token}`;
}
const response = await axios.get(downloadUrl, {
headers,
responseType: 'arraybuffer',
timeout: 60000,
maxRedirects: 5,
maxContentLength: 10 * 1024 * 1024,
});
return new Uint8Array(response.data);
}
/**View on GitHub (pinned to b824cd0a46)
Solutions
- Verify the attachment ID and that it belongs to the current session/account
- Re-authenticate — an expired/invalid token often yields an empty payload
- Align CLI and server versions so the request-download response shape matches
Defensive patterns
Strategy: validation
Validate before calling
const res = await requestDownload(attachmentId);
if (typeof res?.data?.downloadUrl !== 'string') {
throw new Error(`no downloadUrl for attachment ${attachmentId}`);
} Type guard
function hasDownloadUrl(d: unknown): d is { downloadUrl: string } {
return typeof d === 'object' && d !== null
&& typeof (d as any).downloadUrl === 'string';
} Try / catch
try {
await downloadAttachment(attachmentId);
} catch (e) {
if (e.message.includes('no downloadUrl')) {
// attachment missing/expired or token invalid — re-auth and verify ID
} throw e;
} Prevention
- Verify the attachment ID exists before downloading
- Refresh tokens before long download sessions
- Keep client/server API shapes aligned
When it happens
Trigger: Attachment ID does not exist or belongs to another session; token lacks permission; server returns { data: null } or an error envelope instead of { downloadUrl }; API shape change between client and server versions.
Common situations: Downloading an attachment after it was deleted/expired; auth token from a different account; pointing at a server version that renamed the response field.
Related errors
AI-assisted analysis of slopus/happy@b824cd0a46 (2026-08-31).
Data as JSON: /api/errors/0ce3c020ca25abc1.
Report an issue: GitHub.