paperclipai/paperclip · error · Error
Set both namespaced page uploader credential variables
Error message
Set both namespaced page uploader credential variables
What it means
AWS credentials may be provided via namespaced PAPERCLIP_PAGE_AWS_ACCESS_KEY_ID / PAPERCLIP_PAGE_AWS_SECRET_ACCESS_KEY (mapped onto the standard AWS_* names for the upload). This error means exactly one of the pair is set, i.e. they must be provided together or not at all.
Solutions
- Set both PAPERCLIP_PAGE_AWS_ACCESS_KEY_ID and PAPERCLIP_PAGE_AWS_SECRET_ACCESS_KEY together
- Or unset both to fall back to ambient AWS credentials/profile (e.g. PAPERCLIP_PAGE_AWS_PROFILE or default chain)
- Fix the CI secrets mapping so both variables are injected from their paired secrets
- Verify both with: printenv | grep PAPERCLIP_PAGE_AWS
Example fix
// before export PAPERCLIP_PAGE_AWS_ACCESS_KEY_ID=AKIA... // after export PAPERCLIP_PAGE_AWS_ACCESS_KEY_ID=AKIA... export PAPERCLIP_PAGE_AWS_SECRET_ACCESS_KEY=****
Defensive patterns
Strategy: validation
Validate before calling
const k = !!process.env.PAPERCLIP_PAGE_AWS_ACCESS_KEY_ID, s = !!process.env.PAPERCLIP_PAGE_AWS_SECRET_ACCESS_KEY;
if (k !== s) throw new Error("set both or neither of the namespaced page uploader credentials"); Try / catch
try { await main(); } catch (e) { if (e.message.includes("namespaced page uploader credential")) { /* set/unset the credential pair together */ } } Prevention
- Treat the access key and secret as one unit: set or unset both together
- Map CI secrets in pairs and verify both are injected before the publish step
- When rotating credentials, update both variables in the same change
- Prefer a named profile (PAPERCLIP_PAGE_AWS_PROFILE) over hand-set keys to avoid partial configuration
When it happens
Trigger: Setting PAPERCLIP_PAGE_AWS_ACCESS_KEY_ID but forgetting PAPERCLIP_PAGE_AWS_SECRET_ACCESS_KEY (or vice versa) when not relying on ambient AWS credentials/profile.
Common situations: Partial secret injection in CI where only one of the two variables was configured; hand-copying credentials and missing the second line; rotating keys and updating only one variable.
Related errors
- Unable to inspect protocol eval history object
- agentcore_profile_not_found
- agentcore_profile_unavailable
- Assigned remote workspace must be a normalized absolute path
- AWS AgentCore live sessions require a qualified AgentCore…
AI-assisted analysis of paperclipai/paperclip@3f1d897a7c (2026-09-18).
Data as JSON: /api/errors/f4867a2773c7e7c9.
Report an issue: GitHub.
Appendix: source
Thrown at scripts/publish-announcements.ts:89
"--content-type", file.contentType, "--cache-control", file.cacheControl];
}
async function main() {
const { sourceDirectory, staging, publish } = parseAnnouncementPublishArgs(process.argv.slice(2));
const hostPrefix = process.env.PAPERCLIP_PAGE_DEFAULT_PREFIX;
const prepared = await prepareAnnouncementPublish(sourceDirectory, staging, hostPrefix);
const bucket = process.env.PAPERCLIP_PAGE_BUCKET;
const baseUrl = process.env.PAPERCLIP_PAGE_BASE_URL?.replace(/\/+$/, "") ?? "https://pages.paperclip.ing";
const url = `${baseUrl}/${announcementPublishPrefix(staging, hostPrefix)}/current.json`;
const parsed = new URL(url);
if (parsed.protocol !== "https:" || parsed.username || parsed.password || parsed.search || parsed.hash) throw new Error("Invalid public base URL");
console.log(JSON.stringify({ mode: publish ? "publish" : "dry-run", target: staging ? `staging/${staging}` : "production", bucket: bucket ?? "(unset)", url, announcementId: prepared.manifest.announcement?.id ?? null, files: prepared.files }, null, 2));
if (!publish) return;
if (!bucket) throw new Error("Set PAPERCLIP_PAGE_BUCKET before publishing");
const env = { ...process.env };
const key = env.PAPERCLIP_PAGE_AWS_ACCESS_KEY_ID;
const secret = env.PAPERCLIP_PAGE_AWS_SECRET_ACCESS_KEY;
if (Boolean(key) !== Boolean(secret)) throw new Error("Set both namespaced page uploader credential variables");
if (key && secret) {
env.AWS_ACCESS_KEY_ID = key;
env.AWS_SECRET_ACCESS_KEY = secret;
delete env.AWS_SESSION_TOKEN;
if (env.PAPERCLIP_PAGE_AWS_SESSION_TOKEN) env.AWS_SESSION_TOKEN = env.PAPERCLIP_PAGE_AWS_SESSION_TOKEN;
} else if (env.PAPERCLIP_PAGE_AWS_PROFILE) {
delete env.AWS_ACCESS_KEY_ID;
delete env.AWS_SECRET_ACCESS_KEY;
delete env.AWS_SESSION_TOKEN;
env.AWS_PROFILE = env.PAPERCLIP_PAGE_AWS_PROFILE;
}
// Only validated files, assets before manifest; credentials are scoped to AWS.
for (const file of prepared.files) execFileSync("aws", announcementUploadArgs(bucket, file), { env, stdio: "pipe" });
console.log("Uploaded. Checking the public manifest (CDN propagation can take five minutes)…");
for (let attempt = 0; attempt < 23; attempt++) {
try {
const response = await fetch(url, { signal: AbortSignal.timeout(10_000), credentials: "omit", redirect: "error" });
const body = announcementManifestSchema.parse(await response.json());View on GitHub (pinned to 3f1d897a7c)