{"record":{"id":"af888869ce9f4ef1","repo":"coollabsio/coolify","slug":"the-directory-backup-workdir-is-unavailable","errorCode":null,"errorMessage":"The directory backup workdir is unavailable.","messagePattern":"The directory backup workdir is unavailable\\.","errorType":"exception","errorClass":"RuntimeException","httpStatus":null,"severity":"error","filePath":"app/Models/ScheduledVolumeBackup.php","lineNumber":178,"sourceCode":"    }\n\n    public function sourcePath(): string\n    {\n        $target = $this->backupable;\n\n        if ($target instanceof LocalPersistentVolume) {\n            return filled($target->host_path) ? $target->host_path : $target->name;\n        }\n\n        if (! $target instanceof LocalFileVolume || ! $target->is_directory) {\n            throw new \\RuntimeException('The backup target is not a directory or persistent volume.');\n        }\n\n        $path = str($target->fs_path);\n        if ($path->startsWith('.')) {\n            $resource = $this->targetResource();\n            if (! $resource || ! method_exists($resource, 'workdir')) {\n                throw new \\RuntimeException('The directory backup workdir is unavailable.');\n            }\n\n            return $resource->workdir().$path->after('.')->toString();\n        }\n\n        return $path->toString();\n    }\n}\n","sourceCodeStart":160,"sourceCodeEnd":187,"githubUrl":"https://github.com/coollabsio/coolify/blob/70b9acc42467278373e00de77abb40684e25b395/app/Models/ScheduledVolumeBackup.php#L160-L187","documentation":"Thrown by ScheduledVolumeBackup::sourcePath() (app/Models/ScheduledVolumeBackup.php:178). For a directory LocalFileVolume whose fs_path is relative (starts with '.'), the real path is workdir-relative: sourcePath() must call workdir() on the owning resource obtained via targetResource() ($this->backupable?->resource). If that resource is null — the storage row outlived its application/service/database — or the resource class does not have a workdir() method, the path cannot be resolved and the RuntimeException fires.","triggerScenarios":"Running a directory backup where the LocalFileVolume's resource (application, service application, service database) was deleted but the volume/schedule rows remained, or the morphed resource type is a model without a workdir() method; combined with a relative fs_path like './data'.","commonSituations":"Partial cascades that delete the resource but leave file-storage rows; schedules attached to storages of exotic/custom resource types; data imported between instances where resource rows are missing.","solutions":["Restore or re-create the owning resource so backupable->resource resolves, or re-attach the storage to a live resource.","Prefer an absolute fs_path (e.g. /data/... ) for directory storages that get backup schedules — sourcePath() returns it directly without needing workdir().","If the resource is gone for good, delete the orphaned storage and its schedule instead of leaving a backup that always fails."],"exampleFix":"// before\n$path = $backup->sourcePath(); // throws: relative dir path, owning resource gone\n\n// after — ensure an owning resource exists, or store an absolute path\n$resource = $backup->targetResource();\nif ($resource === null || ! method_exists($resource, 'workdir')) {\n    // orphaned: clean up rather than retry forever\n    $backup->delete();\n    return;\n}\n$path = $backup->sourcePath();\n\n// structural fix: use an absolute directory path when creating the storage\n$fileVolume->fs_path = '/data/backups/dir'; // instead of './dir'","handlingStrategy":"type-guard","validationCode":"// Before relying on sourcePath(), ensure a relative directory path has a live owner with workdir()\n$target = $backup->backupable;\nif ($target instanceof \\App\\Models\\LocalFileVolume && str($target->fs_path)->startsWith('.')) {\n    $resource = $target->resource;\n    if ($resource === null || ! method_exists($resource, 'workdir')) {\n        abort(422, 'Directory backup target has no owning resource — re-attach it or use an absolute path.');\n    }\n}\n$path = $backup->sourcePath();","typeGuard":"function hasResolvableWorkdir(\\App\\Models\\ScheduledVolumeBackup $backup): bool\n{\n    $target = $backup->backupable;\n    if (! ($target instanceof \\App\\Models\\LocalFileVolume)) {\n        return true; // volumes resolve via host_path/name\n    }\n    if (! str($target->fs_path)->startsWith('.')) {\n        return true; // absolute path needs no workdir\n    }\n    $resource = $target->resource;\n\n    return $resource !== null && method_exists($resource, 'workdir');\n}","tryCatchPattern":"try {\n    $path = $backup->sourcePath();\n} catch (\\RuntimeException $e) {\n    if (str_contains($e->getMessage(), 'workdir is unavailable')) {\n        // owning resource deleted or lacks workdir(): delete the orphaned schedule or re-attach storage to a live resource\n        $backup->delete();\n        return;\n    }\n    throw $e;\n}","preventionTips":["Prefer absolute fs_path values for directory storages that carry backup schedules — they skip workdir() resolution entirely.","When deleting applications/services, cascade their file storages and backup schedules together so no orphaned relative paths remain.","Health-check scheduled backups by verifying backupable->resource still exists before each run."],"tags":["coolify","backups","polymorphic-relations","file-storage","path-resolution"],"backgroundTag":"missing-related-record","analyzedSha":"70b9acc42467278373e00de77abb40684e25b395","analyzedAt":"2026-08-17T01:41:01.313Z","schemaVersion":2},"datasetVersion":"2026-08-17T04:17:16.089Z"}