From d2900b8857895decd9ed3598b7ccaa25011d24a6 Mon Sep 17 00:00:00 2001 From: Song Seung Hu Date: Fri, 24 Apr 2026 22:04:59 +0900 Subject: [PATCH] Require confirmation for destructive Jules deletion --- Platform/Claude-Code-Prompt-Strict-Ops.md | 4 +-- Platform/Claude-Code-Prompt.md | 4 +-- Platform/Migration-Guide.md | 4 +-- docs/setup-and-test.md | 7 +++- google-jules-control/SKILL.md | 16 +++++++-- .../references/jules-reference.md | 5 ++- google-jules-control/scripts/jules_api.py | 14 ++++++++ tests/test_jules_api.py | 35 +++++++++++++++++++ 8 files changed, 78 insertions(+), 11 deletions(-) diff --git a/Platform/Claude-Code-Prompt-Strict-Ops.md b/Platform/Claude-Code-Prompt-Strict-Ops.md index b44fd9c..2c6deb9 100644 --- a/Platform/Claude-Code-Prompt-Strict-Ops.md +++ b/Platform/Claude-Code-Prompt-Strict-Ops.md @@ -15,7 +15,7 @@ Rules: - Use `--scope-note` and `--non-goal` when extra boundaries matter. - Use `summary`, `cleanup-report --markdown`, `close-ready-report --markdown`, and `notify-close-plan --markdown` for user-facing communication. - Check `gh-auth-check --compact` before merge-aware cleanup. -- Never delete or close a Jules session without explicit user confirmation. +- Never close, cancel, or delete a Jules session without explicit user confirmation. - Summarize long JSON outputs into concise operational messages. Sequence: @@ -41,7 +41,7 @@ Rules: - Use `--scope-note` and `--non-goal` when extra boundaries matter. - Use `summary`, `cleanup-report --markdown`, `close-ready-report --markdown`, and `notify-close-plan --markdown` for user-facing communication. - Check `gh-auth-check --compact` before merge-aware cleanup. -- Never delete or close a Jules session without explicit user confirmation. +- Never close, cancel, or delete a Jules session without explicit user confirmation. - Summarize long JSON outputs into concise operational messages. Sequence: diff --git a/Platform/Claude-Code-Prompt.md b/Platform/Claude-Code-Prompt.md index 5d4e924..5a9de3f 100644 --- a/Platform/Claude-Code-Prompt.md +++ b/Platform/Claude-Code-Prompt.md @@ -16,7 +16,7 @@ When working with Google Jules: - If the task is ambiguous or appears to require out-of-scope work, ask a clarifying question instead of broadening the task. - Use `--scope-note` and `--non-goal` when you need extra task boundaries. - Use `summary`, `cleanup-report`, `close-ready-report`, and `notify-close-plan --markdown` to keep users informed. -- Never close or delete a Jules session without explicit user confirmation. +- Never close, cancel, or delete a Jules session without explicit user confirmation. - Before merge-aware cleanup actions, verify GitHub auth with `gh-auth-check --compact`. - Summarize long JSON outputs into concise user-facing updates. @@ -44,7 +44,7 @@ When working with Google Jules: - If the task is ambiguous or appears to require out-of-scope work, ask a clarifying question instead of broadening the task. - Use `--scope-note` and `--non-goal` when you need extra task boundaries. - Use `summary`, `cleanup-report`, `close-ready-report`, and `notify-close-plan --markdown` to keep users informed. -- Never close or delete a Jules session without explicit user confirmation. +- Never close, cancel, or delete a Jules session without explicit user confirmation. - Before merge-aware cleanup actions, verify GitHub auth with `gh-auth-check --compact`. - Summarize long JSON outputs into concise user-facing updates. diff --git a/Platform/Migration-Guide.md b/Platform/Migration-Guide.md index 8d65732..23a4be9 100644 --- a/Platform/Migration-Guide.md +++ b/Platform/Migration-Guide.md @@ -52,7 +52,7 @@ Use this document as the shared baseline when adapting the `google-jules-control - Keep `google-jules-control/scripts/jules_api.py` as the single source of truth for Jules API control - Keep `.env` and `JULES_API_KEY` as the primary auth path - Keep merge checks based on `gh` -- Keep session closure behind explicit user confirmation +- Keep session close, cancel, and delete actions behind explicit user confirmation ### Platform Migration Checkpoints @@ -68,7 +68,7 @@ Use this document as the shared baseline when adapting the `google-jules-control - support for absolute-path links - markdown rendering behavior 4. Operational safety - - user confirmation before deletion + - user confirmation before close, cancel, or delete - merge-state verification - protection against leaking `.env` diff --git a/docs/setup-and-test.md b/docs/setup-and-test.md index ed77685..a94d5e0 100644 --- a/docs/setup-and-test.md +++ b/docs/setup-and-test.md @@ -84,8 +84,13 @@ python3 google-jules-control/scripts/jules_api.py summary --session sessions/SES 5. 테스트 세션 정리 / Clean up the test session +이 단계는 되돌릴 수 없는 세션 삭제입니다. 테스트 세션을 삭제해도 된다고 확인한 뒤 실행합니다. +This is irreversible session deletion. Run it only after confirming the test session can be deleted. + ```bash -python3 google-jules-control/scripts/jules_api.py delete-session --session sessions/SESSION_ID +python3 google-jules-control/scripts/jules_api.py delete-session \ + --session sessions/SESSION_ID \ + --confirm-delete DELETE_JULES_SESSION ``` ## 자주 쓰는 명령 / Common Commands diff --git a/google-jules-control/SKILL.md b/google-jules-control/SKILL.md index 9e2286a..dd06a63 100644 --- a/google-jules-control/SKILL.md +++ b/google-jules-control/SKILL.md @@ -247,10 +247,15 @@ Suggested flow: ```bash python3 scripts/jules_api.py summary --session sessions/SESSION_ID -python3 scripts/jules_api.py cancel-session --session sessions/SESSION_ID ``` -State clearly that this is implemented as session deletion and is not a reversible pause. +State clearly that cancel is implemented as session deletion and is not a reversible pause. After explicit user confirmation, run: + +```bash +python3 scripts/jules_api.py cancel-session \ + --session sessions/SESSION_ID \ + --confirm-delete DELETE_JULES_SESSION +``` ### Example: Check active sessions and close a merged one safely @@ -461,7 +466,7 @@ python3 scripts/jules_api.py close-ready-report --repo-filter owner/repo --requi - Use `--non-goal` when you need to forbid refactors, cleanup, dependency changes, schema changes, or other adjacent work. - If the task is ambiguous, prefer a follow-up question over a broader instruction. - Prefer goal-driven follow-ups with explicit verification targets over vague “improve this” style prompts. -- Treat `cancel-session` as destructive. It maps to session deletion, not a reversible pause. +- Treat `delete-session` and `cancel-session` as destructive. They map to irreversible session deletion, not a reversible pause, and require explicit user confirmation plus `--confirm-delete DELETE_JULES_SESSION`. - For merged-work cleanup, always follow this order: inspect session, verify merged PR status, ask the user, then run `close-merged-session`. - Use `--require-all-merged` when the session output contains more than one pull request URL. - Use `list-unmerged-sessions` when the user wants a single view of work that is still open on GitHub. @@ -514,6 +519,11 @@ The bundled script lives at `scripts/jules_api.py` and supports: Run `python3 scripts/jules_api.py --help` or `python3 scripts/jules_api.py --help` for flags. +Destructive commands intentionally require safety tokens: + +- `delete-session` and `cancel-session`: `--confirm-delete DELETE_JULES_SESSION` +- `close-merged-session`: `--confirm-close CLOSE_MERGED_SESSION` + ## Credential Setup Create a `.env` file and add: diff --git a/google-jules-control/references/jules-reference.md b/google-jules-control/references/jules-reference.md index a47b4c4..d1b5cdf 100644 --- a/google-jules-control/references/jules-reference.md +++ b/google-jules-control/references/jules-reference.md @@ -48,7 +48,7 @@ Important session fields: Practical note: - There is no dedicated REST `resume` endpoint in the current public API. If a session is waiting, resume it by approving a pending plan or sending a follow-up message. -- There is no reversible REST `cancel` endpoint distinct from deletion. Deleting the session is the closest cancellation-style action. +- There is no reversible REST `cancel` endpoint distinct from deletion. Deleting the session is the closest cancellation-style action, so `delete-session` and `cancel-session` require `--confirm-delete DELETE_JULES_SESSION` after explicit user confirmation. - The public API does not document a reliable remaining-usage or quota-balance endpoint for end users. Handle quota failures explicitly, but do not guess a remaining amount. Activity types to watch: @@ -107,6 +107,8 @@ The bundled `jules_api.py` script automates this with: - `check-pr-readiness --session ...` - `request-pr-rework --session ... --markdown` - `notify-close-plan --session ... --markdown` +- `delete-session --session ... --confirm-delete DELETE_JULES_SESSION` +- `cancel-session --session ... --confirm-delete DELETE_JULES_SESSION` - `close-merged-session --session ... --confirm-close CLOSE_MERGED_SESSION` Implementation detail: @@ -118,6 +120,7 @@ Implementation detail: - `doctor` separates `api_ready`, `cli_ready`, and `merge_ready`. `ready=yes` means at least one control path is available. - `close-ready-report` distinguishes between `candidates` and `cautionCandidates`. Treat caution entries as manual-review items, not automatic close targets. - `close-merged-session` refuses `caution` sessions by default. Only use `--allow-caution-close` after explicit user approval. +- `delete-session` and `cancel-session` refuse to run without the explicit delete token because both are irreversible deletion flows. Pagination contract: diff --git a/google-jules-control/scripts/jules_api.py b/google-jules-control/scripts/jules_api.py index 6e1ebcc..a2ff1c8 100644 --- a/google-jules-control/scripts/jules_api.py +++ b/google-jules-control/scripts/jules_api.py @@ -26,6 +26,7 @@ OPEN_STATES = ACTIVE_STATES | WAITING_STATES PR_URL_RE = re.compile(r"https://github\.com/[^/\s]+/[^/\s]+/pull/\d+") CLOSE_CONFIRM_TOKEN = "CLOSE_MERGED_SESSION" +DELETE_CONFIRM_TOKEN = "DELETE_JULES_SESSION" SUCCESSFUL_CHECK_CONCLUSIONS = {"SUCCESS", "NEUTRAL", "SKIPPED"} FAILED_CHECK_CONCLUSIONS = {"FAILURE", "TIMED_OUT", "CANCELLED", "STARTUP_FAILURE", "ACTION_REQUIRED", "STALE"} PENDING_CHECK_STATUSES = {"EXPECTED", "IN_PROGRESS", "PENDING", "QUEUED", "REQUESTED", "WAITING"} @@ -1024,6 +1025,11 @@ def list_sessions(args: argparse.Namespace) -> None: def delete_session(args: argparse.Namespace) -> None: session_name = normalize_session_name(args.session) + if args.confirm_delete != DELETE_CONFIRM_TOKEN: + fail( + "Session deletion is irreversible and is not a pause. " + f"Re-run with --confirm-delete {DELETE_CONFIRM_TOKEN} after explicit user approval." + ) api_request("DELETE", f"/{session_name}") print(json.dumps({"ok": True, "deleted": session_name}, ensure_ascii=False)) @@ -1793,6 +1799,10 @@ def build_parser() -> argparse.ArgumentParser: delete_session_parser = subparsers.add_parser("delete-session", help="Delete a Jules session.") delete_session_parser.add_argument("--session", required=True, help="Session id or sessions/ resource name.") + delete_session_parser.add_argument( + "--confirm-delete", + help=f"Required safety token. Must be exactly {DELETE_CONFIRM_TOKEN}.", + ) delete_session_parser.set_defaults(func=delete_session) cancel_session_parser = subparsers.add_parser( @@ -1800,6 +1810,10 @@ def build_parser() -> argparse.ArgumentParser: help="Delete a Jules session as a cancel-style action. This is permanent.", ) cancel_session_parser.add_argument("--session", required=True, help="Session id or sessions/ resource name.") + cancel_session_parser.add_argument( + "--confirm-delete", + help=f"Required safety token. Must be exactly {DELETE_CONFIRM_TOKEN}.", + ) cancel_session_parser.set_defaults(func=delete_session) get_session_parser = subparsers.add_parser("get-session", help="Fetch one Jules session.") diff --git a/tests/test_jules_api.py b/tests/test_jules_api.py index af9ac34..7fdf52b 100644 --- a/tests/test_jules_api.py +++ b/tests/test_jules_api.py @@ -239,6 +239,41 @@ def test_close_merged_session_refuses_caution_without_override(self) -> None: self.assertEqual(1, api_request.call_count) + def test_delete_session_requires_explicit_confirmation_token(self) -> None: + args = argparse.Namespace(session="sessions/123", confirm_delete=None) + + with mock.patch.object(jules_api, "api_request") as api_request: + with redirect_stderr(io.StringIO()) as stderr: + with self.assertRaises(SystemExit): + jules_api.delete_session(args) + + self.assertEqual(0, api_request.call_count) + self.assertIn("DELETE_JULES_SESSION", stderr.getvalue()) + + def test_delete_session_allows_explicit_confirmation_token(self) -> None: + args = argparse.Namespace(session="sessions/123", confirm_delete="DELETE_JULES_SESSION") + + with mock.patch.object(jules_api, "api_request", return_value={}) as api_request: + with redirect_stdout(io.StringIO()): + jules_api.delete_session(args) + + api_request.assert_called_once_with("DELETE", "/sessions/123") + + def test_delete_session_parser_accepts_confirmation_token(self) -> None: + parser = jules_api.build_parser() + + args = parser.parse_args( + [ + "delete-session", + "--session", + "sessions/123", + "--confirm-delete", + "DELETE_JULES_SESSION", + ] + ) + + self.assertEqual("DELETE_JULES_SESSION", args.confirm_delete) + def test_doctor_reports_api_ready_without_merge_ready(self) -> None: args = argparse.Namespace(compact=True) output = io.StringIO()