Skip to content

feat: run /idd-all in Claude Code cloud sessions — REST egress and reads, (PR, attended) — 3.6.0 (#416) - #426

Open
kiki830621 wants to merge 17 commits into
mainfrom
idd/416-cloud-session-idd-all
Open

kiki830621 wants to merge 17 commits into
mainfrom
idd/416-cloud-session-idd-all

Conversation

@kiki830621

@kiki830621 kiki830621 commented Oct 25, 2026 •

Copy link
Copy Markdown
Member

Refs #416

Summary

讓 /idd-all 能在 Claude Code 雲端 session(web/手機/排程)裡跑(Spectra change cloud-session-idd-all,plugin 3.6.0)。雲端 session 會擋 GitHub GraphQL,所以 gh issue|pr|label|repo porcelain 和 gh auth status 都會失敗,gh api REST 則可以用。

  • 偵測:scripts/lib/cloud-session.sh。CLAUDE_CODE_REMOTE 必須正好是 true 才算雲端;CLAUDE_CODE_SESSION_ATTENDED 正好是 1 才算 attended,沒設就是 unattended
  • 寫入:gh-egress.sh 的 gate 都不動(attestation、四道 net、empty-body guard、exit band),通過之後一律經 lib/gh_egress_rest.py 走 REST,本機也一樣。stdout 只有一行 URL。gh 和 REST 行為不同的地方照 gh:label 必須已存在、milestone 不分大小寫比對 open milestone、移除不存在的 label 什麼都不做。不支援的旗標 exit 14,不會被悄悄丟掉
  • 讀取:lib/gh-rest.sh(issue-view/issue-list/pr-list/repo-view)輸出和 porcelain 同名的欄位,會自動翻頁,雲端拒絕時附 hint:。check-existing-work、check-diagnosis-readiness、process-attachments、idd-worktree、check-closed-without-summary 都改用它
  • idd-all:雲端一律走 PR path(direct commit 會隨 container 消失)。新增 (PR, attended) tuple;雲端的 --no-pr 會被覆寫並印出通知;(PR, unattended) 缺 ralph-loop 時 abort,不降級成 direct-commit。只有 verify PASS 才開 PR,有 blocking findings 時雲端仍 push branch。建 branch 和開 PR 直接呼叫 REST
  • idd-verify:雲端只跑 Claude(codexEnabled: false),報告會寫 Codex leg not run — cloud session (degraded: Claude-only)
  • Skills/文件:9 個支援的 skill 加 runtime note、4 個不支援的 skill(idd-find、idd-report、idd-discuss、idd-ask)在雲端會停下並說明原因。新 reference references/claude-code-remote-tools.md 收 porcelain→REST 對照、不支援清單和 setup script。CHANGELOG/README/版本 3.6.0

本機的模式解析不變。本機看得到的行為變化(都已列在 CHANGELOG):寫入改走 REST;Phase 5 拿不到 verify 結果就不 push;auth gate 是 gh auth status || gh api user。

Fixed before merge(verify)

四輪審查(第 1 輪 4 個 lens、第 2 輪 devil's advocate、第 3 輪獨立 re-verify、第 4 輪複查),再加一輪 2 位 reviewer 對整個 diff 的最終全面審查(含 3,600 組隨機參數 fuzz)與複查,都只用 Claude;最後沒有 blocking。報告:Verify、最終審查 addendum。

  • security HIGH:issue URL 的 owner/repo 可以夾帶 #、?、..,讓 edit <url> --remove-label x 變成 DELETE /repos/o/r。現在嚴格解析,違規 exit 14
  • gate parity HIGH(3.5.0 就存在):wrapper 和 gh 對取值旗標的解析不一致,gate 掃的不是實際送出的內容。現在每個 transport 接受的取值旗標,wrapper 都同樣讀下一個 token 當值(有 drift guard),-b=X 等形式也一致
  • fail-open HIGH:${VERIFY_STATE:-PASS} 會在 shell 狀態遺失時開 PR。現在缺值一律停在 push 之前
  • empty-body guard bypass(bug: gh-egress.sh edit 接受空 body → 靜默清空 issue body(資料遺失) #275 起就存在):gh 只送最後一個 --body/--body-file,guard 卻檢查合併後的內容,--body "<誘餌>" --body "" 會清空 issue 本文。現在每次送出只接受一個 body 來源
  • Phase 0.5 沒有真的停下:PR setup 用的是未定義的 abort,讀不到 title 時照樣建出以錯誤 JSON 命名的 branch。現在用區塊內定義的 stop,測試會實際執行這個區塊
  • 最終審查:Phase 5 在 PATH_AXIS 遺失時原本 exit 0、什麼都不 push(雲端會丟 commit),現在停下並提醒;idd-implement 在 /idd-all 底下不再於 verify 前開 PR(Simple 與 Plan tier 都是);名稱以 - 開頭的 body file 原本會以空字串通過 gate(3.5.0 就存在);audit 的作者過濾原本沒作用;create 必須有 body
  • 安全 finding 一律不自動 push(commit 可能帶著 secret);idd-list 需要的 server-side 排序、全量 comments、mergeable、--closed-since 已補進 gh-rest;git push 和 gh api 失敗會讓流程停下;edit-comment --repo 會先驗證

Test plan

  • bash plugins/issue-driven-dev/scripts/run-all-tests.sh:雲端環境變數沒設和有設(CLAUDE_CODE_REMOTE=true CLAUDE_CODE_SESSION_ATTENDED=1)各跑一次,都是 63 / 65。兩個失敗是 idd-repo-map、idd-tree-lock,以 root 執行時 origin/main 上同樣會失敗
  • 新套件 cloud-session(134;Phase 0.5 PR setup/Phase 0.6/Phase 5 在暫存 repo + bare origin 實際執行)和 gh-rest(56);gh-egress(194)改成斷言 REST argv 與 stdin JSON
  • 已在雲端 container 實測:GraphQL 被擋、REST 可用,gh-rest.sh 能讀真實 API;feat: 在 Claude Code 雲端 session 裡也能跑 /idd-all(gh GraphQL 403、plugin 未載入)(sister of #400) #416 的 Verify comment 是經 gh-egress.sh 的 REST transport 實際貼出的
  • 尚未驗證:從 setup script 開新的雲端 session,以及端到端的雲端 /idd-all
  • merge 前建議在本機跑 /idd-verify --pr <this PR>,補上 Codex 跨模型那一條腿

Merge 後用 /idd-close 416 結案,並 archive Spectra change。

https://claude.ai/code/session_01PJZJ8sbcVVqqLngBfkGNUq


Generated by Claude Code

claude added 12 commits October 25, 2026 19:31
… Code cloud sessions (#416)

Hand-written change artifacts (the spectra CLI is not available in the
cloud container): proposal, design, spec deltas for the new
cloud-session-runtime capability and the modified idd-pr-hitl-modes
mode resolution, and tasks.

Refs #416

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PJZJ8sbcVVqqLngBfkGNUq
…pes (#416)

scripts/lib/cloud-session.sh decides "cloud session" (CLAUDE_CODE_REMOTE
exactly "true") and "attended" (CLAUDE_CODE_SESSION_ATTENDED exactly "1";
anything else is unattended, so an unwatched session never waits on a
question).

scripts/lib/gh-rest.sh (logic in gh_rest.py) reads issues, issue lists,
pull requests and repositories through `gh api` REST and returns the same
field names and shapes as `gh ... --json`, pages until a short page or the
limit, and adds a hint line when a cloud session cannot reach the repo.
Read-only: no write subcommands.

Refs #416

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PJZJ8sbcVVqqLngBfkGNUq
…416)

Porcelain `gh issue create|comment|edit` is GraphQL underneath, which
Claude Code cloud sessions block, so every issue write failed there. The
wrapper keeps every gate (attestation, privacy / mention / tier-floor
nets, empty-body guard) and its exit band; only the final dispatch changes:
lib/gh_egress_rest.py turns the forwarded `gh issue` arguments into
`gh api` REST calls, in every environment.

Stdout is one URL line, so `URL=$(gh-egress.sh create ...)` callers are
unaffected. The body reaches gh on stdin, never in argv. Flags the REST
transport does not support are refused with exit 14 instead of being
dropped; an unknown milestone title is refused the same way; a failing
gh call passes its own exit code through.

Refs #416

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PJZJ8sbcVVqqLngBfkGNUq
…efuse in cloud (#416)

check-existing-work.sh, check-diagnosis-readiness.sh, process-attachments.sh
and idd-worktree.sh used porcelain `gh pr list` / `gh repo view` /
`gh issue view`, which are GraphQL and fail with 403 in Claude Code cloud
sessions. They now read through scripts/lib/gh-rest.sh. Field names are
unchanged, so each script's matching logic is untouched; the test shims
serve REST-shaped data and refuse porcelain, proving the normalisation.
idd-worktree reads the title from the repo root and keeps its bare-branch
fallback (a title-path test is added — it had no coverage).

check-existing-work's error labels become `pr-list --state open` /
`issue-view N`; idd-close Step 1.5, which fails closed by matching those
labels, now matches `issue-view` as well as the old `gh issue view`.

discussions_api.GitHub.graphql refuses up front in a cloud session
(Discussions have no REST API) instead of sending a request that 403s.

Refs #416

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PJZJ8sbcVVqqLngBfkGNUq
…e reference (#416)

- references/claude-code-remote-tools.md: detection signals, the porcelain
  -> REST table for the in-scope skills, cloud-session PR routes, the
  unsupported surfaces with what each prints, and the setup script.
- A one-line runtime note in 13 skills: the 9 on the idd-all path (plus
  idd-list) point to the reference; idd-find, idd-report, idd-discuss and
  idd-ask stop and say they are unsupported. Skill bodies are not
  rewritten.
- idd-all: a cloud session forces the PR path (direct-commit work dies
  with the container) and takes the interaction from the attended signal
  (absent = unattended); --no-pr is overridden with a notice; the rule
  sits before fork detection, which always 403s there. In a cloud session
  (PR, unattended) without ralph-loop aborts after unwinding the branch
  instead of degrading. The auth gate uses `gh api user` (`gh auth status`
  fails in cloud sessions). Local resolution is unchanged — the test
  executes the skill's own resolution block per scenario.
- idd-verify: Claude-only in a cloud session (codexEnabled: false, no
  codex-pro governance), stated in the engine line and process gaps.

Refs #416

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PJZJ8sbcVVqqLngBfkGNUq
…416)

- gh_egress_rest.py read body files in text mode, which turns CRLF into
  LF; gh sent the file's bytes. It now reads with newline="" (test added).
- correct-flow's fake gh (added in 3.5.0) understood only `gh issue edit`;
  it now also takes the transport's `api … -X PATCH --input -` JSON
  payload, and the assertion names that call.
- mention-markdown dispatched `comment` without an issue number, which the
  porcelain stub never checked; the REST transport refuses it, so the
  fixture passes one.

Refs #416

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PJZJ8sbcVVqqLngBfkGNUq
README (plugin + root) documents running /idd-all in a Claude Code cloud
session and the setup script that installs the plugins and the egress
parser; the runtime-compatibility policy and claude-code-tools.md point
to the cloud reference; CHANGELOG 3.6.0; version 3.5.0 -> 3.6.0. The
change artifacts record the idd-close label consumer and the byte-exact
body-file read found during implementation.

Refs #416

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PJZJ8sbcVVqqLngBfkGNUq
…te parity, gh semantics (#416)

Four independent reviewers (requirements, logic, security, regression;
Claude-only, no Codex in the container). Fixed:

- security HIGH: an issue URL such as https://github.com/o/r#/issues/5 made
  the REST transport send DELETE /repos/o/r (and PATCH /repos/o/r).
  Issue URLs, --repo values and hosts are now parsed strictly; `.`/`..`
  segments, `#`, `?` and `user@` are refused before any request. Fetched
  issue / PR numbers are checked before they enter an API path.
- gh-egress.sh re-read a flag-shaped value (`-b "--repo=..."`) as a flag
  while gh took it as the body, so the privacy, mention and empty-body nets
  could be skipped (present on 3.5.0). The wrapper now reads the next token
  as the value, as gh does.
- REST transport keeps gh's behavior where REST differs: labels must exist
  (REST would create them), milestones match open milestones ignoring
  case, `edit --milestone ""` removes it, removing an absent label is a
  no-op, `-b=x` is `x`. On failure stdout stays empty (gh api prints its
  JSON error there) and stderr carries a hint; non-UTF-8 args exit 12.
- gh-rest: `--sort updated`, `--closed-since`, full comments in issue-list,
  `mergeable` in pr-list, empty body as "" — what idd-list needs.
- check-closed-without-summary.sh reads through gh-rest (the sixth script
  the user approved), so idd-list --audit-closes works in the cloud.
- idd-all: Phase 0.6's cloud stop is an explicit exit (the `abort`
  convention is undefined in that shell and fell through to the degrade);
  Step 0.4 / branch setup / unwind / PR creation call REST directly; a PR
  is opened only after verify PASS, and a cloud run with blocking findings
  still pushes the branch; PR body and report say when verify was
  Claude-only; auth gate is `gh auth status || gh api user` (App tokens
  cannot call /user).
- idd-verify: codex-pro governance is explicitly conditional, codexEnabled
  is a boolean, the engine line no longer claims Codex in the cloud.
- Docs: not-migrated skills, experimental status, idd-list mapping, merge
  is never authorised; spec deltas, design and tasks record the round.

Fixtures that spoke porcelain to the converted code (acquisition-truncation,
gate-live-path, actionability-gate Phase 5) now answer REST paths.

Refs #416

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PJZJ8sbcVVqqLngBfkGNUq
Devil's-advocate findings on the round-1 fixes:

- gh-egress.sh: every value-taking flag the REST transport accepts now
  takes the next token as its value in the wrapper too (drift-guarded),
  so `--remove-label -b --body-file=x` can no longer slip a body file
  past the gates; `-b=X` / `-t=X` / `-F=X` read as X; edit-comment
  validates --repo (OWNER/REPO, no dot segments) and the comment id.
- idd-all Phase 5: a missing VERIFY_STATE is never read as PASS; push
  failures and unreadable default branch / issue title stop the run
  instead of feeding gh's error body into a slug or PR base; the
  unattended verify-fix loop pushes the branch in a cloud session before
  giving up; Phase 0.6's cloud abort clears the unattended flag.
- gh-rest --closed-since accepts YYYY-M-D and offset timestamps,
  normalised to UTC; check-closed-without-summary reports a refused
  argument separately from an auth / network failure.
- CHANGELOG, design (D5, D8, D10), spec and tasks record the round.

Refs #416

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PJZJ8sbcVVqqLngBfkGNUq
Independent re-verify of the round-2 commit:

- gh-egress.sh: a dispatching call takes one body source. gh and the
  transport send only the last --body / --body-file, while the #275
  guard checked them all joined, so `--body "<decoy>" --body ""` sent
  an empty body. `check` (sends nothing) still scans several inputs.
  Leak tests now assert the privacy-net exit exactly.
- idd-all Phase 0.5 PR setup stops through a defined helper instead of
  the undefined `abort`, which fell through into `git checkout -b` with
  a slug made from gh's error body; executed by the cloud-session suite.
- Phase 0.6 stops instead of degrading when the default branch cannot
  be read; a cloud run does not auto-push when a blocking finding is a
  security finding.
- gh-rest --closed-since reads a timestamp without a zone as UTC.

Refs #416

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PJZJ8sbcVVqqLngBfkGNUq
Verify round 4 (re-check of 75ef92b) found nothing blocking; its two
non-blocking points:

- An attended run that hands back a blocking security finding sets
  VERIFY_STATE=BLOCKING_SECURITY; Phase 5 then pushes nothing, cloud
  included, and says the commits must be checked for secrets first.
- The local Phase 0.6 degrade keeps the unattended flag on purpose (the
  caller asked for an unattended run, often /loop); the comment and the
  notice now say so.

Refs #416

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PJZJ8sbcVVqqLngBfkGNUq
Refs #416

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PJZJ8sbcVVqqLngBfkGNUq
…, docs (#416)

- idd-all Phase 5 has an else branch: a lost PATH_AXIS stops the run
  (with a cloud warning) instead of exiting 0 with nothing pushed.
- Phase 0.6 refuses to run on a lost (PATH_AXIS, INTERACTION) or, in PR
  mode, a lost BRANCH, instead of skipping the gate.
- idd-all passes `ORCHESTRATED BY /idd-all` to idd-implement on the PR
  path and idd-implement skips Step 5.5, so no PR is opened before
  verify; the old "naturally compatible" note was wrong.
- Frontmatter names the cloud (PR, attended) mode; Trace 4 matches the
  current degrade output; new Trace 5 for (PR, attended).
- idd-verify `--engine codex` stops in a cloud session; the reviewer
  launch task notes there is no Codex call there.

Refs #416

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PJZJ8sbcVVqqLngBfkGNUq
Independent review of the whole branch diff (scripts lens): nothing
blocking introduced here; these were found and are closed:

- gh-egress.sh reads body files with `cat --` and refuses a failed read
  (exit 12). A `-`-named file was read as cat options, scanned as "",
  and sent in full with --allow-empty-body. Present on 3.5.0.
- check-closed-without-summary reads authorAssociation as well as
  author_association, so the author filter binds on the audit path.
- create needs a body, as non-interactive `gh issue create` did.
- Issue numbers are length-bounded (no ValueError on 5000 digits); the
  unused gh_rest.is_cloud_session is removed; a missing label message
  mentions an unreachable repository; the remove-assignee DELETE path
  is tested.

Refs #416

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PJZJ8sbcVVqqLngBfkGNUq
Re-check of e692d4a: the no-early-PR directive reached idd-implement on
the Simple path only. On the Plan tier idd-all goes through /idd-plan,
whose Step 6 called idd-implement with `#NNN` alone, so Step 5.5 could
still open a PR before verify. idd-all now hands the directive to
/idd-plan and idd-plan forwards it with --pr; a drift guard pins all
three skills. The Failure Modes table and CHANGELOG say that lost-state
stops are re-runs (the unattended flag stays).

Refs #416

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PJZJ8sbcVVqqLngBfkGNUq
…atement (#416)

Refs #416

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PJZJ8sbcVVqqLngBfkGNUq
Observed while tagging the verified snapshot for this change: the
session proxy refuses tag pushes and POST repos/R/git/refs (HTTP 403,
"Write access to this GitHub API path is not permitted through this
proxy"). idd-verify Step 4.5 already degrades to its graceful-skip
warning; the reference, the step and the CHANGELOG now say so and that
the tag is made from a local session.

Refs #416

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PJZJ8sbcVVqqLngBfkGNUq
This was referenced Oct 26, 2026
kiki830621 pushed a commit that referenced this pull request Oct 26, 2026
Version left to the merge: PR #426 and #424 both claim 3.6.0, PR #418
claims 3.5.1.

Refs #411, #412, #413

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PJZJ8sbcVVqqLngBfkGNUq
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants