amruthpillai/reactive-resume · error · Error
Private storage writes are not supported by the local filesy
Error message
Private storage writes are not supported by the local filesystem backend. Configure S3 to store private attachments.
What it means
LocalStorageService.write throws a plain Error when called with private:true. The local filesystem backend has no access-control boundary, so private attachments (e.g. application documents) cannot be stored safely. The message tells operators to configure S3. Unlike the oRPC errors above, this is a generic Error — it will surface as HTTP 500 unless wrapped.
Source
Thrown at packages/api/src/features/storage/service.ts:142
const fullPath = this.resolvePath(prefix);
try {
const files = await fs.readdir(fullPath, { recursive: true });
return files.map((file) => join(prefix, file));
} catch (error: unknown) {
// If directory doesn't exist, return empty array
if (error && typeof error === "object" && "code" in error && error.code === "ENOENT") {
return [];
}
throw error;
}
}
async write({ key, data, private: isPrivate }: StorageWriteInput): Promise<void> {
if (isPrivate) {
throw new Error(
"Private storage writes are not supported by the local filesystem backend. Configure S3 to store private attachments.",
);
}
const fullPath = this.resolvePath(key);
await fs.mkdir(dirname(fullPath), { recursive: true });
await fs.writeFile(fullPath, data);
}
async read(key: string): Promise<StorageReadResult | null> {
const fullPath = this.resolvePath(key);
try {
const [arrayBuffer, stats] = await Promise.all([fs.readFile(fullPath), fs.stat(fullPath)]);
return {
data: arrayBuffer,
size: stats.size,View on GitHub (pinned to 3a5b12e2a4)
Solutions
- Configure S3-compatible storage: set S3_ACCESS_KEY_ID, S3_SECRET_ACCESS_KEY, S3_BUCKET (and optionally S3_ENDPOINT/REGION/FORCE_PATH_STYLE), or start the seaweedfs compose service.
- If S3 is unavailable in your environment, disable the feature that requests private writes rather than allowing the write to be attempted.
- Gate the private-write code path on a capability check (e.g. storage.getCapabilities().supportsPrivate) and degrade gracefully.
- Confirm getStorageService() actually selects S3 after the env change by hitting the storage healthcheck endpoint.
Example fix
// before: unconditional private write
await storage.write({ key, data, contentType: 'application/pdf', private: true });
// after: guard on backend capability
if (!storage.supportsPrivateWrites()) {
return c.json({ error: 'Private attachments require S3 storage.' }, 501);
}
await storage.write({ key, data, contentType: 'application/pdf', private: true }); Defensive patterns
Strategy: validation
Validate before calling
function supportsPrivateWrites(): boolean {
return Boolean(env.S3_ACCESS_KEY_ID && env.S3_SECRET_ACCESS_KEY && env.S3_BUCKET);
}
if (isPrivate && !supportsPrivateWrites()) throw new ConfigError('Configure S3 for private attachments.'); Type guard
function isPrivateWriteInput(i: StorageWriteInput): i is StorageWriteInput & { private: true } {
return i.private === true;
} Try / catch
try { await storage.write({ key, data, contentType, private: true }); }
catch (e) {
if (/local filesystem backend/.test(String((e as Error).message))) { /* degrade: disable feature or use S3 */ }
throw e;
} Prevention
- Provision S3/SeaweedFS before enabling private-attachment features.
- Gate private writes on a backend capability check.
- Add a startup healthcheck that fails fast if private features are on without S3.
When it happens
Trigger: Any code path that requests a private write (StorageWriteInput.private = true) while S3_ACCESS_KEY_ID / S3_SECRET_ACCESS_KEY / S3_BUCKET are unset, so getStorageService() returned the LocalStorageService. Most commonly: uploading an application/cover-letter attachment marked private.
Common situations: Local/dev deployments that omitted S3/SeaweedFS config but enabled features requiring private attachments; a feature flag turning on private uploads without provisioning S3; CI without SeaweedFS running a flow that uploads private docs.
Related errors
AI-assisted analysis of amruthpillai/reactive-resume@3a5b12e2a4 (2026-08-12).
Data as JSON: /api/errors/499d065a2cb9f10f.
Report an issue: GitHub.