Commit Graph

2 Commits

Author SHA1 Message Date
Neil 126d8046a9 fix: harden post-merge review findings and close CI gaps (#145)
Follow-ups to the #126/#133/#135 merges, from the review of each.

From #135 (draft attachments):
- upload.ts: completedFileIds compacted its result with .filter() while the
  caller indexed it positionally against the staged list. If Slack omitted an
  id for any file but the last, every subsequent id shifted up a slot and the
  wrong file was attached to the draft. Ids are now positionally aligned with
  undefined holes preserved, and the response is only trusted when its length
  matches what was sent.
- message-draft-actions.ts: cleanupOrphanedDraftFiles takes an input object,
  matching the codebase convention and clearing the new max-params warning.
- drafts.ts: correct ensureDraftOk's comment — both transports already throw
  on ok:false, so it is defence-in-depth, not the load-bearing guard it
  claimed to be.
- Reformat the files the PR left failing oxfmt.

From #126 (wrapped error causes):
- String() on a cause is not total: a null-prototype object threw inside the
  CLI's own catch handlers, turning a printable error into a crash. Route all
  stringification through safeString().
- Cap the cause walk at 8 levels so a long chain cannot emit a multi-hundred-
  kilobyte single-line message.
- Collapse a cause that merely restates its parent ("fetch failed: fetch
  failed").

Docs (AGENTS.md requires SKILL.md track the code):
- README/SKILL.md/llms.txt document --attach on draft create/update, including
  update's merge-into-existing semantics.
- README: note --no-unfurl works with --schedule, which was implemented and
  tested in #133 but left out of the flag list.

CI:
- Run typecheck and format:check. Neither ran anywhere, so a type error or
  formatting drift could reach main — #135 arrived with oxfmt already failing.
- Trigger on push to main. CI was pull_request-only, so the merge commits main
  is actually built from were never validated.
- Gate the release workflow on typecheck too.
2026-09-01 23:33:53 -07:00
Jaehoon Choi 7b52a1f107 feat: support file attachments on Slack-native drafts (#135)
* feat: support file attachments on Slack-native drafts

Slack-native drafts (message draft create/update) accepted no --attach:
createDraft's input lacked fileIds, the CLI exposed no option, and the
upload helper was message-only (completeUploadExternal bound the file to
a channel, so it could not serve a draft that has no message yet).

- Add uploadFileForDraft to src/slack/upload.ts: shares upload staging
  with uploadLocalFileToSlack but completes without channel_id, returning
  the file id (files.completeUploadExternal's channel_id is optional).
- Wire fileIds through createDraft (updateDraft already supported it).
- Add a repeatable --attach to `message draft create` / `update`; update
  merges new uploads into the draft's existing file_ids.

The safe-mode compose editor path remains intentionally out of scope:
draft-editor.html has no attachment UI, so redirectSendToDraft still
rejects --attach there.

Co-Authored-By: Claude <noreply@anthropic.com>

* refactor: share attach helpers and batch draft uploads

Address ce-code-review findings on PR #135:

- Extract normalizeAttachPaths + collectOptionValue into src/cli/options.ts.
  The two helpers were duplicated byte-for-byte across message-actions.ts,
  message-command.ts, and the message-draft-* modules (review #4, #6).
- Replace the per-file draft upload loop with uploadFilesForDraft: stage every
  file first, then a single completeUploadExternal. A staging failure leaves
  nothing completed, and Slack discards staged-but-uncompleted uploads, so a
  later --attach failure can no longer orphan private files (review #1).
- Add a mergeFileIds de-duplication test for overlapping file ids, and update
  the mock/assertions for the batched completion order (review #5).

Co-Authored-By: Claude <noreply@anthropic.com>

* fix: draft 첨부파일 orphan 방지 - auth 재시도 재사용 + 실패 정리

리뷰 #2/#3 대응. draft에 파일을 첨부할 때 뒤따르는 draft API
호출이 실패하면 이미 올린 파일이 비공개로 orphan 되는 문제를 막는다.

#2 (auth 재시도 재실행): createDraft/updateDraftAction에서
uploadedFileIds를 withAutoRefresh의 work() 바깥 클로저에
메모이제이션한다. drafts.create/update가 invalid_auth로 실패해
work()가 재실행될 때 upload를 다시 돌리지 않고 올린 file id를
재사용한다. 첫 업로드 파일이 두 번째 업로드에 밀려 orphan/중복되던
문제를 해결한다.

#3 (stale 실패 orphan): createDraft/updateDraft에 ensureDraftOk를
추가해 ok:false(stale conflict, denied 등)를 throw하게 했다(기존엔
null로 삼킴). cleanupUploadedDraftFiles(files.delete best-effort)
헬퍼를 추가하고, action 전체를 try/catch로 감싸 최종 실패 시 업로드한
새 파일만 정리 후 rethrow한다. update는 existing file_ids는
건드리지 않고 새로 올린 파일만 정리한다.

테스트: createContext에 failOnce/failWith/retryOnAuth 주입 옵션을
추가하고 #2 create/update 재시도, #3 create/update 실패 정리,
첨부 없으면 files.delete 미호출 시나리오 5개를 추가했다.

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-09-01 23:30:03 -07:00