sipeed/picoclaw · error
resolve lifecycle delete workspace for skill %q: workspace i
Error message
resolve lifecycle delete workspace for skill %q: workspace is required
What it means
ApplyLifecycleState (pkg/evolution/lifecycle.go:53) must build <workspace>/skills/<name>/SKILL.md to delete a skill transitioning to "deleted". The workspace root comes from profile.WorkspaceID, falling back to inferWorkspaceFromPaths, which only succeeds when paths.RootDir is laid out literally as <workspace>/state/evolution. When both are empty the delete is refused instead of guessing a path.
Source
Thrown at pkg/evolution/lifecycle.go:53
if idle > 365*24*time.Hour && profile.RetentionScore < 0.1 {
return SkillStatusDeleted
}
}
return profile.Status
}
func ApplyLifecycleState(paths Paths, profile SkillProfile, next SkillStatus) error {
if next != SkillStatusDeleted {
return nil
}
workspace := profile.WorkspaceID
if workspace == "" {
workspace = inferWorkspaceFromPaths(paths)
}
if workspace == "" {
return fmt.Errorf("resolve lifecycle delete workspace for skill %q: workspace is required", profile.SkillName)
}
if err := skills.ValidateSkillName(profile.SkillName); err != nil {
return fmt.Errorf("resolve lifecycle delete skill name: %w", err)
}
skillPath := filepath.Join(workspace, "skills", profile.SkillName, "SKILL.md")
err := os.Remove(skillPath)
if errors.Is(err, os.ErrNotExist) {
return nil
}
return err
}
func RunLifecycleOnce(store *Store, paths Paths, workspace string, now time.Time) (LifecycleRunSummary, error) {
if store == nil {
return LifecycleRunSummary{}, nil
}
View on GitHub (pinned to 49183d7e8d)
Solutions
- Set WorkspaceID on stored profiles (or backfill it) before running the lifecycle
- Construct Paths with evolution.NewPaths(workspace, "") so RootDir resolves to <workspace>/state/evolution
- Re-layout the custom RootDir as <workspace>/state/evolution
- Run profile sync first to populate workspace_id on legacy profiles
Example fix
// before
paths := evolution.Paths{RootDir: "/tmp/custom-state"}
err := evolution.ApplyLifecycleState(paths, profile, evolution.SkillStatusDeleted)
// after
paths := evolution.NewPaths(workspace, "") // RootDir = <workspace>/state/evolution
err := evolution.ApplyLifecycleState(paths, profile, evolution.SkillStatusDeleted) Defensive patterns
Strategy: validation
Validate before calling
func lifecycleWorkspaceResolvable(paths evolution.Paths, profile evolution.SkillProfile) bool {
if profile.WorkspaceID != "" {
return true
}
root := filepath.Clean(paths.RootDir)
return filepath.Base(root) == "evolution" && filepath.Base(filepath.Dir(root)) == "state"
}
if !lifecycleWorkspaceResolvable(paths, profile) {
// set profile.WorkspaceID or rebuild paths via evolution.NewPaths(workspace, "") Try / catch
if err := evolution.ApplyLifecycleState(paths, profile, evolution.SkillStatusDeleted); err != nil {
if strings.Contains(err.Error(), "workspace is required") {
// populate WorkspaceID on profiles or fix the state-dir layout
}
} Prevention
- Always build Paths with NewPaths(workspace, "") instead of hand-assembling RootDir
- Backfill workspace_id on profiles written by older versions before enabling lifecycle runs
- Keep the state layout as <workspace>/state/evolution — inference depends on the literal path shape
When it happens
Trigger: profile.WorkspaceID == "" (profiles written before the field existed or cleared by a sync) combined with Paths built with a custom RootDir — its base directory is not `evolution` under a `state` directory (temp dirs, relocated state, test fixtures).
Common situations: Embedding RunLifecycleOnce with a custom state layout; profiles migrated from an older store schema; unit tests using t.TempDir() roots; running the lifecycle against a copy of the state directory.
Related errors
- workspace is not initialized
- Workspace path is required.
- feishu channel is not supported on 32-bit architectures (arm
- session already completed
- must be in range 1-65535
AI-assisted analysis of sipeed/picoclaw@49183d7e8d (2026-08-15).
Data as JSON: /api/errors/901bacf177e9b2aa.
Report an issue: GitHub.