fix(cron): validate session_key in cron add to prevent silent runtime failures (#126)

When creating a cron job via `cron add`, if no session_key was provided
(neither via --session-key flag nor CC_SESSION_KEY env), the job was
created with an empty session_key. At runtime, ExecuteCronJob would fail
silently because it couldn't determine the target platform from an empty
session key.

Add server-side validation and auto-detection in handleCronAdd:
- If session_key is empty, attempt to auto-detect from the engine's
  active interactive sessions (works when exactly one session exists)
- If auto-detection fails, return a clear error to the client

Add Engine.ActiveSessionKeys() helper method to expose active session
keys for the auto-detection logic. Uses platform != nil check (instead
of agent session alive check) so sessions are discoverable even before
an agent process is started.
This commit is contained in:
Sean authored and GitHub committed 2026-03-15 18:20:07 +08:00
1 parent cd4a85e858
commit bbd5d56197
2 files changed
+33 -1

No files matched your search

+20 -1
View File
@@ -240,10 +240,29 @@ func (s *APIServer) handleCronAdd(w http.ResponseWriter, r *http.Request) {
return
}
// Resolve session_key: use provided, or auto-detect from active sessions
sessionKey := req.SessionKey
if sessionKey == "" {
s.mu.RLock()
engine := s.engines[project]
s.mu.RUnlock()
if engine != nil {
keys := engine.ActiveSessionKeys()
if len(keys) == 1 {
sessionKey = keys[0]
slog.Debug("auto-detected session_key for cron job", "session_key", sessionKey)
}
}
}
if sessionKey == "" {
http.Error(w, "session_key is required: set CC_SESSION_KEY env, pass --session-key, or ensure exactly one active session exists", http.StatusBadRequest)
return
}
job := &CronJob{
ID: GenerateCronID(),
Project: project,
SessionKey: req.SessionKey,
SessionKey: sessionKey,
CronExpr: req.CronExpr,
Prompt: req.Prompt,
Exec: req.Exec,
+13
View File
@@ -533,6 +533,19 @@ func (e *Engine) ProjectName() string {
return e.name
}
// ActiveSessionKeys returns the session keys of all active interactive sessions.
func (e *Engine) ActiveSessionKeys() []string {
e.interactiveMu.Lock()
defer e.interactiveMu.Unlock()
var keys []string
for key, state := range e.interactiveStates {
if state.platform != nil {
keys = append(keys, key)
}
}
return keys
}
// ExecuteCronJob runs a cron job by injecting a synthetic message into the engine.
// It finds the platform that owns the session key, reconstructs a reply context,
// and processes the message as if the user sent it.