yikart/AiToEarn · error
Relay uploadSign returned no uploadUrl: ${JSON.stringify(sig
Error message
Relay uploadSign returned no uploadUrl: ${JSON.stringify(signResult)} What it means
uploadFileFromLocalUrl asks the Relay service to sign an asset upload via relayMediaService.uploadSign and then PUTs the file to the returned presigned URL. If the sign response contains no uploadUrl (missing field, null, or an unexpected error payload), the code cannot proceed and throws with the full signResult serialized for debugging.
Source
Thrown at project/aitoearn-backend/apps/aitoearn-ai/src/core/ai/relay-media/relay-media-resolver.service.ts:154
private isCommonResponse<T>(body: RelayCommonResponse<T> | T): body is RelayCommonResponse<T> {
return typeof body === 'object' && body !== null && 'data' in body
}
private async uploadFileFromLocalUrl(localUrl: string): Promise<string> {
const filename = basename(new URL(localUrl).pathname)
const fileResponse = await axios.get(localUrl, { responseType: 'arraybuffer' })
const contentType = fileResponse.headers['content-type'] || 'application/octet-stream'
const size = (fileResponse.data as ArrayBuffer).byteLength
const signResult = await this.post<UploadSignResult>('/api/assets/uploadSign', {
filename,
type: AssetType.Temp,
size,
})
if (!signResult.uploadUrl) {
throw new Error(`Relay uploadSign returned no uploadUrl: ${JSON.stringify(signResult)}`)
}
await axios.put(signResult.uploadUrl, fileResponse.data, {
headers: { 'Content-Type': contentType },
timeout: this.config?.timeout,
})
await this.post(`/api/assets/${signResult.id}/confirm`, {})
this.logger.debug({ localUrl, relayUrl: signResult.url }, 'Uploaded local media to relay')
return signResult.url
}
private escapeRegExp(value: string): string {
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
}
}
View on GitHub (pinned to d3aa8bea5b)
Solutions
- Inspect the JSON in the message: it contains the full signResult and usually reveals the real Relay error (e.g. {code, message}).
- Verify Relay credentials (RELAY_API_KEY) match the environment: China keys with *.aitoearn.cn URLs, international keys with *.aitoearn.ai URLs — mismatches often yield 401 bodies without uploadUrl.
- Check the Relay service's uploadSign endpoint health/logs; confirm storage presigning is configured.
- Confirm the Relay client contract (uploadSign response type) matches the deployed Relay version; upgrade or pin versions.
- Add a fallback: surface the signResult error to the caller instead of silently failing the whole text resolution.
Example fix
// before
if (!signResult.uploadUrl) {
throw new Error(`Relay uploadSign returned no uploadUrl: ${JSON.stringify(signResult)}`)
}
// after
if (!signResult.uploadUrl) {
this.logger.error(`Relay uploadSign failed: ${JSON.stringify(signResult)}`)
throw new AppException(ResponseCode.RelayUploadSignFailed)
} Defensive patterns
Strategy: try-catch
Validate before calling
const res = await relay.uploadSign({ filename, type, size })
if (!res || typeof res.uploadUrl !== 'string' || res.uploadUrl.length === 0) {
throw new Error(`uploadSign failed: ${JSON.stringify(res)}`)
} Type guard
function hasUploadUrl(r: unknown): r is { uploadUrl: string } {
return !!r && typeof r === 'object' && 'uploadUrl' in r && typeof (r as any).uploadUrl === 'string' && (r as any).uploadUrl.length > 0
} Try / catch
try {
const url = await uploadFileFromLocalUrl(...)
} catch (err) {
if (err.message.includes('no uploadUrl')) {
logger.error('Relay sign failed', err.message) // inspect embedded signResult JSON
// verify Relay credentials/env pairing, then retry or surface a friendly error
}
throw err
} Prevention
- Keep RELAY_API_KEY and Relay server URL in the same environment (cn key ↔ aitoearn.cn, ai key ↔ aitoearn.ai).
- Add a startup health check that signs a tiny dummy upload.
- Monitor Relay uploadSign error rates and alert on missing uploadUrl.
- Pin Relay client/server contract versions in sync.
When it happens
Trigger: uploadFileFromLocalUrl (invoked via resolveText) receives a signResult object without an uploadUrl property — e.g. the Relay backend returned an error envelope, an auth failure body, or a schema change removed/renamed uploadUrl.
Common situations: Relay server deployed with an older/newer uploadSign contract; expired or mismatched API credentials causing a silent error body instead of an HTTP error; network proxy stripping the field; Relay storage backend (S3/OSS presigning) misconfigured so signing fails but the endpoint still returns 200.
Related errors
- 上传视频失败,失败原因1:
- 分片上传视频失败: ${error.response?.data?.error?.message || error.me
- 直接上传视频失败: ${error.response?.data?.error?.message || error.me
- 上传失败: ${uploadResponse.statusText}
- Failed to fetch image: ${response.status} ${response.statusT
AI-assisted analysis of yikart/AiToEarn@d3aa8bea5b (2026-08-31).
Data as JSON: /api/errors/22d8eeba6b9a3177.
Report an issue: GitHub.