fix(core): strip workspace prefix from cron/heartbeat session keys (#474)
In multi-workspace mode, stored session keys may be prefixed with the workspace path (e.g. "/home/user/project:slack:C123:U456"). The naive first-colon split produced a path as the platform name, causing ExecuteCronJob and ExecuteHeartbeat to fail with "platform not found". Add a fallback loop that searches for a registered platform name within the key and strips the prefix before proceeding. Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
1 parent
6dd2155f87
commit
326fa258ff
2 files changed
+75
No files matched your search
@@ -814,6 +814,20 @@ func (e *Engine) ExecuteCronJob(job *CronJob) error {
|
||||
break
|
||||
}
|
||||
}
|
||||
// Fallback: in multi-workspace mode the stored session key may be prefixed
|
||||
// with the workspace path (e.g. "/home/user/project:slack:C123:U456").
|
||||
// Search for a known platform name within the key and strip the prefix.
|
||||
if targetPlatform == nil {
|
||||
for _, p := range e.platforms {
|
||||
needle := ":" + p.Name() + ":"
|
||||
if idx := strings.Index(sessionKey, needle); idx >= 0 {
|
||||
targetPlatform = p
|
||||
platformName = p.Name()
|
||||
sessionKey = sessionKey[idx+1:] // strip workspace prefix
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if targetPlatform == nil {
|
||||
return fmt.Errorf("platform %q not found for session %q", platformName, sessionKey)
|
||||
}
|
||||
@@ -998,6 +1012,20 @@ func (e *Engine) ExecuteHeartbeat(sessionKey, prompt string, silent bool) error
|
||||
break
|
||||
}
|
||||
}
|
||||
// Fallback: in multi-workspace mode the stored session key may be prefixed
|
||||
// with the workspace path (e.g. "/home/user/project:slack:C123:U456").
|
||||
// Search for a known platform name within the key and strip the prefix.
|
||||
if targetPlatform == nil {
|
||||
for _, p := range e.platforms {
|
||||
needle := ":" + p.Name() + ":"
|
||||
if idx := strings.Index(sessionKey, needle); idx >= 0 {
|
||||
targetPlatform = p
|
||||
platformName = p.Name()
|
||||
sessionKey = sessionKey[idx+1:] // strip workspace prefix
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if targetPlatform == nil {
|
||||
return fmt.Errorf("platform %q not found for session %q", platformName, sessionKey)
|
||||
}
|
||||
|
||||
@@ -8107,6 +8107,53 @@ func TestExecuteCronJob_ResolvesCronReplyTarget(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestExecuteCronJob_WorkspacePrefixedSessionKey(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
store, err := NewCronStore(dir)
|
||||
if err != nil {
|
||||
t.Fatalf("NewCronStore() error = %v", err)
|
||||
}
|
||||
scheduler := NewCronScheduler(store)
|
||||
|
||||
platform := &stubCronReplyTargetPlatform{
|
||||
stubPlatformEngine: stubPlatformEngine{n: "slack"},
|
||||
}
|
||||
agentSession := newResultAgentSession("done")
|
||||
agent := &resultAgent{session: agentSession}
|
||||
|
||||
e := NewEngine("test", agent, []Platform{platform}, "", LangEnglish)
|
||||
defer e.cancel()
|
||||
e.cronScheduler = scheduler
|
||||
|
||||
// Simulate a session key that was stored with a workspace prefix
|
||||
// (as happens in multi-workspace mode).
|
||||
prefixedKey := "/home/user/workspace/myproject:slack:C123:U456"
|
||||
job := &CronJob{
|
||||
ID: "job-ws",
|
||||
SessionKey: prefixedKey,
|
||||
Prompt: "daily standup",
|
||||
Description: "Standup",
|
||||
}
|
||||
if err := store.Add(job); err != nil {
|
||||
t.Fatalf("store.Add() error = %v", err)
|
||||
}
|
||||
|
||||
if err := e.ExecuteCronJob(job); err != nil {
|
||||
t.Fatalf("ExecuteCronJob() with workspace-prefixed key error = %v", err)
|
||||
}
|
||||
|
||||
// The platform should have received the cron start notice and agent reply.
|
||||
sent := platform.getSent()
|
||||
if len(sent) < 1 {
|
||||
t.Fatalf("expected at least one message sent to platform, got %d", len(sent))
|
||||
}
|
||||
|
||||
// Stored session key must remain unchanged.
|
||||
if job.SessionKey != prefixedKey {
|
||||
t.Fatalf("job.SessionKey = %q, want unchanged %q", job.SessionKey, prefixedKey)
|
||||
}
|
||||
}
|
||||
|
||||
func TestExtractSessionKeyParts(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
|
||||
Reference in new issue
Block a user