fix: keep task completion, cancel, pause, and undo honest

Pending earlier-revision jobs no longer skip completion repair; late
cancels preserve real exits; begin clears pause; partial undo keeps
snapshots; permissions and worktree failure paths are hardened.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
WHC
2026-10-28 22:09:47 +08:00
co-authored by Cursor
parent d971cb02b0
commit acd6f1d998
7 changed files with 111 additions and 17 deletions
+18 -2
View File
@@ -26,6 +26,14 @@ static int locked_work(void *p, volatile int *cancel, cst_tool_result_t *out) {
static int instant(void *p, volatile int *cancel, cst_tool_result_t *out) {
(void)p; (void)cancel; out->out = strdup("archived output"); out->out_len = strlen(out->out); return 0;
}
/* Runs to a failed exit even if cancel raced after the side effect. */
static int fail_then_note_cancel(void *p, volatile int *cancel, cst_tool_result_t *out) {
(void)p;
out->out = strdup("command failed"); out->out_len = strlen(out->out);
out->exit_code = 7;
if (cancel) *cancel = 1; /* late cancel must not rewrite exit 7 → 130 */
return 0;
}
static void write_text(const char *dir, const char *name, const char *text) {
char p[8192]; assert(!os_path_join(p, sizeof(p), dir, name));
FILE *f = fopen(p, "wb"); assert(f); assert(fputs(text, f) >= 0); assert(!fclose(f));
@@ -72,11 +80,19 @@ int main(void) {
assert(!cst_runtime_finish(r, "partial", "explicit partial", 0, error, sizeof(error)));
assert(!cst_runtime_finish(r, "completed", "must not overwrite partial", 0, error, sizeof(error)));
s = cst_runtime_json(r); assert(s && strstr(s, "explicit partial")); free(s);
assert(!cst_runtime_controls(r, 1, 0, 1));
/* Pause sticks across reopen; begin() clears it so a new turn can run. */
assert(!cst_runtime_controls(r, 0, 1, 1));
cst_runtime_close(r);
r = cst_runtime_open(path); assert(r);
assert(!cst_runtime_check(r, 0, 0, 0));
assert(!cst_runtime_check(r, 0, 1, 1));
s = cst_job_read(r, id, 0); assert(s && strstr(s, "cancelled")); free(s);
unsigned long next = cst_runtime_begin(r, "after pause");
assert(cst_runtime_check(r, next, 1, 1));
assert(!cst_job_start(r, "shell", "", 1, fail_then_note_cancel, NULL, NULL, id,
sizeof(id)));
s = cst_job_read(r, id, 2000);
assert(s && strstr(s, "\"status\":\"failed\"") && strstr(s, "\"exit_code\":7"));
free(s);
cst_runtime_close(r);
write_text(dir, "session.task-v2.json", "{\"revision\":7,\"sequence\":1,\"status\":\"running\",\"jobs\":[{\"job_id\":\"job-7-1\",\"status\":\"running\",\"kind\":\"external\"}]}");
r = cst_runtime_open(path); assert(r && !cst_runtime_jobs_pending(r));
+15
View File
@@ -93,6 +93,21 @@ static void test_undo(void) {
EXPECT(cst_undo_snapshot(g_ws, sid, "t4", "../evil") != 0, "reject ..");
EXPECT(cst_undo_snapshot(g_ws, sid, "t4", "/etc/passwd") != 0,
"reject absolute");
/* Partial restore must keep the snapshot so a retry is possible. */
EXPECT(cst_undo_snapshot(g_ws, sid, "t5", "keep.txt") == 0, "snap keep");
wfile("keep.txt", "after\n");
{
char target[512];
snprintf(target, sizeof(target), "%s/keep.txt", g_ws);
unlink(target);
EXPECT(symlink("elsewhere", target) == 0, "make symlink");
EXPECT(cst_undo_restore_last(g_ws, sid, rep, sizeof(rep)) != 0,
"partial undo fails");
EXPECT(strstr(rep, "symlink") != NULL, "report names symlink skip");
EXPECT(cst_undo_count(g_ws, sid) == 1, "snapshot kept after partial fail");
unlink(target);
}
}
static void test_perms(void) {