Skip to content

feat(channel): channel membership control (members/add/remove/sync)#244

Merged
lmjabreu merged 9 commits into
mainfrom
lmjabreu/focused-boyd-17ebbc
May 22, 2026
Merged

feat(channel): channel membership control (members/add/remove/sync)#244
lmjabreu merged 9 commits into
mainfrom
lmjabreu/focused-boyd-17ebbc

Conversation

@lmjabreu
Copy link
Copy Markdown
Contributor

@lmjabreu lmjabreu commented May 22, 2026

Summary

Adds per-user and per-group membership control to tw channel. Follows the plan in docs/CHANNEL-MEMBERSHIP-PLAN.md, with the command surface revised in review.

tw channel members <ch-ref>                          # list (default)
tw channel members add    <ch-ref> <ref...>          # users and/or group:<ref>
tw channel members remove <ch-ref> <ref...>
tw channel members set    <ch-ref> <ref...> [--apply] [--include-self]
  • Refs can be mixed: tw channel members add general luis@doist.com group:Design id:789.
  • group:<ref> is one-shot expansion — adds the group's current members at call time; the group is not persistently linked to the channel, and users added later to the group will not auto-join. Surfaced in --help for add and set.
  • set replaces the channel's membership with the resolved set. Dry-run by default; pass --apply to mutate.
  • set refuses to remove the acting user unless --include-self is also passed.

What's in the PR

One file per command, mirroring the rest of src/commands/channel/ (list.ts, threads.ts, etc.):

Layer File Notes
Ref parsing src/lib/refs.ts resolveChannelMemberRefs handles group: prefix; reuses resolveUserRefs + resolveGroupRef; preserves input order across mixed refs; resolves user and group lookups concurrently
API wrappers src/lib/api.ts addUsersToChannel / removeUsersFromChannel + spinner messages
Shared helpers src/commands/channel/membership-helpers.ts mutateChannelMembership, fetchUsersByIds (via getOptionalBatchData), logExpansion, groupsFullyInChannel
List src/commands/channel/members.ts listChannelMembers
Add src/commands/channel/add.ts Thin wrapper around mutateChannelMembership
Remove src/commands/channel/remove.ts Same shape as add
Set src/commands/channel/set.ts Dry-run-by-default, --apply, --include-self guard
Registration src/commands/channel/index.ts members is a command group; list is its { isDefault: true } subcommand
Tests src/commands/channel/{members,add,remove,set}.test.ts + src/lib/refs.test.ts One test file per command; new helper-level coverage for resolveChannelMemberRefs
Skill docs src/lib/skills/content.ts Quick Reference rows + group-expansion caveat; regenerated skills/twist-cli/SKILL.md
Scope fix src/lib/auth-provider.ts Added channels:write and channels:remove to READ_WRITE_SCOPES (see side discoveries)

CHANGELOG is not edited — semantic-release will generate it from the conventional-commit messages on the next release.

End-to-end test

Ran against the real Doist workspace (ws:1585) on a throwaway channel "Testing Group Setting Via CLI" (id:837185), with group id:32017 "25Q3|Engineering: Resharding" (3 users, none of them me (Luis)) and user id:252356 Ben (not in the group, not me (Luis)):

Step Command Result
Baseline tw channel members <ch> 1 member (just me (Luis)) ✓
Dry-run add tw channel members add <ch> group:32017 id:252356 --dry-run Preview: 4 users to add (3 from group + Ben), dedupe correct ✓
Apply add tw channel members add <ch> group:32017 id:252356 "Added 4 users … (now 5 members)" ✓
Verify tw channel members <ch> All 5 listed; "Groups fully in channel" correctly identifies Resharding ✓
Remove user tw channel members remove <ch> id:252356 "Removed 1 user … (now 4 members)" ✓
Set self-guard tw channel members set <ch> id:252356 Refused with INVALID_VALUE (would remove me (Luis)) ✓
Set dry-run tw channel members set <ch> id:252356 id:447642 Preview: +1 / -3, dry-run by default ✓
Set apply tw channel members set <ch> … --apply Channel reset to {me (Luis), Ben}; "Set … +1 / -3 (now 2 members)" ✓
Restore tw channel members remove <ch> id:252356 Back to baseline ✓

Channel was deleted afterwards via the Twist UI.

Note: the live commands above were run with the earlier tw channel add/remove/sync surface. The behaviour and refs are identical under the renamed tw channel members add/remove/set nesting; the post-rename build was smoke-tested via --help output and the full unit-test suite.

Side discoveries

Found while developing/testing — flagged so reviewers know they exist:

  1. Fixed in this PRchannels:write and channels:remove were missing from READ_WRITE_SCOPES in src/lib/auth-provider.ts. Without them every channel mutation returned INSUFFICIENT_SCOPE. Existing logged-in users will need to re-run tw auth login to pick up the new scopes.
  2. Out of scope, follow-up neededtw channel create and tw channel delete don't exist in the CLI despite the SDK exposing both. End-to-end testing required creating/deleting the channel in the Twist UI. Worth adding to mirror tw groups create/delete.
  3. Out of scope, follow-up neededtw users appears to return users outside the current workspace. Test User1 (id:2342) showed up in tw users output but the channel API rejected the add with User 2342 is not part of workspace 1585. The list scope of tw users likely needs a workspace filter.

Review history

  • doistbot: 15 inline comments (2 P1, 12 P2, 1 P3) — all addressed except one deferred refactor (extracting a shared mutation flow with groups/members.ts); see response.
  • @scottlovegrove (1st pass): split commands into separate files; split tests by command — addressed in 3f3652f.
  • @scottlovegrove (2nd pass): nest commands under members; rename syncset; switch interfacetype on ChannelMemberRefs; fix <ref><channel-ref> in skill docs; reset stray package-lock.json diff — addressed in fc5dda2 (plus a follow-up lockfile fix after the initial reset used a stale local main).

Checks

  • npm run type-check
  • npm run lint
  • npm test (677 passing, including new helper-level tests for resolveChannelMemberRefs)
  • npm run build && npm run sync:skill
  • Manual end-to-end (see above)
  • CI green (lint, test, SKILL.md Sync, Validate Title)

lmjabreu and others added 2 commits May 22, 2026 10:27
Parse mixed user / group:<ref> lists for channel membership commands.
Groups expand to their current userIds at call time (one-shot, not a
persistent link); the caveat is surfaced later in command help.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- src/lib/api.ts: wrap channels.addUsers / removeUsers with spinner
  messages and helpers (addUsersToChannel / removeUsersFromChannel).
- src/commands/channel/members.ts: list members + groups-fully-in-channel
  hint, add/remove with group: expansion, sync with dry-run-by-default,
  --apply, and --include-self self-removal guard.
- src/commands/channel/index.ts: register the four subcommands with
  examples and the one-shot group-expansion caveat in --help.
- src/commands/channel/members.test.ts: 15 tests covering the cases in
  CHANNEL-MEMBERSHIP-PLAN.md.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@lmjabreu
Copy link
Copy Markdown
Contributor Author

Closing while work is in progress; will reopen when ready for review.

@lmjabreu lmjabreu closed this May 22, 2026
lmjabreu and others added 2 commits May 22, 2026 10:34
Add Quick Reference rows and a Notes paragraph explaining the group:
ref expansion (one-shot, not a persistent link), sync's dry-run default,
and --include-self. Regenerated skills/twist-cli/SKILL.md.

Also pulls a fresh npm install (resolves prior @doist/cli-core/auth
type-check errors on main).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Without these, tw channel add/remove/sync (and any future channel
mutations like create/delete/update) fail with INSUFFICIENT_SCOPE.

Discovered while testing the new channel-membership commands locally.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@lmjabreu lmjabreu reopened this May 22, 2026
@lmjabreu lmjabreu changed the title feat(channel): channel membership control feat(channel): channel membership control (members/add/remove/sync) May 22, 2026
@lmjabreu lmjabreu requested a review from scottlovegrove May 22, 2026 09:56
@lmjabreu lmjabreu marked this pull request as ready for review May 22, 2026 09:56
Copy link
Copy Markdown
Member

@doistbot doistbot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This PR introduces comprehensive channel membership control to the CLI, adding commands to list, add, remove, and sync members using both user and one-shot group references. The feature provides a highly flexible and powerful tool for managing channel participation across the workspace. There are a few areas that need refinement, including updating batch API access to adhere to repository guidelines, replacing sequential network network waterfalls with concurrent fetching, extracting shared membership mutation logic, and expanding test coverage for the new reference parsers and output flags.

Share FeedbackReview Logs

Comment thread src/commands/channel/members.ts Outdated
const responses = await client.batch(...calls)
const map = new Map<number, WorkspaceUser>()
userIds.forEach((id, i) => {
const user = responses[i]?.data
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P1] AGENTS.md mandates that you should never access .data directly on a batch result, as this bypasses API error handling. Replace this manual loop with the buildOptionalBatchNameMap helper from src/lib/api.ts (e.g., return buildOptionalBatchNameMap(userIds, responses, 'user')).

Comment thread src/commands/channel/members.ts Outdated
}

if (options.ndjson) {
console.log(formatNdjson([{ ...channel, members, groupsFullyInChannel: fullyInChannel }]))
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] --ndjson always emits the full channel object here, even without --full, while the --json path emits the slim { id, name, workspaceId, members, groupsFullyInChannel } payload by default. That makes the machine-readable contract depend on which flag the user picked and leaves no way to get non-full NDJSON output. Build the same default payload for NDJSON and only switch to the full object when options.full is set.

Comment thread src/lib/refs.ts Outdated
throw new CliError('MISSING_USERS', 'Provide at least one user or group:<ref> reference.')
}

const userRefs: string[] = []
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] Splitting refs into userRefs and groupRefs loses the original mixed order. For example, ['group:Backend', 'alice'] resolves as alice first and the group members second, so the resulting added/removed arrays and dry-run output no longer match the input order. If input-order dedupe is part of the contract, resolve the refs in a single pass (or preserve an ordered expansion list) instead of batching all users first.

Comment thread src/commands/channel/members.ts Outdated
refs,
workspaceId,
)
const requested = dedupe(requestedIds)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] resolveChannelMemberRefs already deduplicates IDs internally via a Set before returning them. This dedupe call is redundant, and the local helper function can be removed entirely.

Comment thread src/commands/channel/members.ts Outdated
? `No new members added to "${channel.name}" (already in channel).`
: `No members removed from "${channel.name}" (none of the users were in channel).`

if (expandedFrom.length > 0) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P3] This logic for logging group expansions is duplicated exactly in syncChannelMembers at line 322. Consider extracting it to a shared helper function.

Comment thread src/lib/refs.ts Outdated
}
}

if (userRefs.length > 0) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] User and group references are resolved sequentially, causing a network waterfall (one API call per group). Execute resolveUserRefs and all resolveGroupRef calls concurrently with Promise.all.

vi.restoreAllMocks()
apiMocks.getCurrentWorkspaceId.mockResolvedValue(1)
apiMocks.getTwistClient.mockResolvedValue({
workspaceUsers: { getUserById: mockGetUserById },
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] mockGetUserById is a bare vi.fn() without a return value, causing the implementation's getUserById calls to return undefined. Because mockBatch resolves blindly regardless of arguments, the construction of the batch requests is effectively untested. Consider having mockGetUserById return a dummy request object and asserting that mockBatch actually receives those objects.

apiMocks.getCurrentWorkspaceId.mockResolvedValue(1)
apiMocks.getTwistClient.mockResolvedValue({
workspaceUsers: { getUserById: mockGetUserById },
channels: { getChannel: mockGetChannel },
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] mockGetChannel is wired up here but never exercised because the --full flag is completely untested across add, remove, and sync. Adding a test for --full will ensure the secondary API call and JSON formatting are properly verified.

Comment thread src/commands/channel/members.test.ts Outdated
expect(apiMocks.addUsersToChannel).toHaveBeenCalledWith(500, [4, 5])
})

it('expands group:<ref> and dedupes', async () => {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P1] This test doesn't actually verify group expansion or dedupe. Both behaviors are precomputed in the mocked resolveChannelMemberRefs result, and there isn't any companion src/lib/refs.test.ts coverage for the new helper. Please add a focused helper-level test for mixed user + group: refs, input-order dedupe, and invalid group: input; otherwise the core feature added in this PR is protected only by mocks.

Comment thread src/commands/channel/members.test.ts Outdated
})
})

describe('tw channel remove', () => {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] The new remove subcommand only gets side-effect assertions here. Its separate --dry-run, --json, and --full branches aren't exercised at all, so regressions in those user-facing modes will slip through. Add at least one dry-run case and one machine-output case for remove.

lmjabreu and others added 2 commits May 22, 2026 11:35
- refs.ts: preserve input order across mixed user/group: refs; resolve
  user refs and all group refs concurrently in one Promise.all.
- members.ts: use getOptionalBatchData (don't bypass batch error
  handling); drop redundant dedupe (resolveChannelMemberRefs already
  dedupes via a Set); parallelize independent calls in mutate, list,
  and sync; run add+remove concurrently in sync; ndjson now honours
  --full like json does (slim default, raw on --full); extract a small
  logExpansion helper used by both mutate and sync.
- members.test.ts: tighten mockGetUserById to return tagged requests
  so we can assert batch() composition; add tests for --full,
  --ndjson, remove --dry-run, remove --json, remove --full.
- refs.test.ts: add helper-level coverage for resolveChannelMemberRefs
  (mixed order, dedupe across users + group expansion, empty group:
  rejection, case-insensitive prefix).

Deferred (out of scope, will reply on PR): extracting a shared
membership-mutation flow between groups/members.ts and
channel/members.ts.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@lmjabreu lmjabreu force-pushed the lmjabreu/focused-boyd-17ebbc branch from 36deeab to 3f3652f Compare May 22, 2026 10:36
@lmjabreu
Copy link
Copy Markdown
Contributor Author

Addressed doistbot's feedback in 02c9f04..3f3652f (squashed mentally: review fixes + chore to untrack a lock file).

Done:

  • [P1] members.ts:42 — use getOptionalBatchData instead of accessing .data directly.
  • [P1] members.test.ts:137 — added focused helper-level coverage for resolveChannelMemberRefs in src/lib/refs.test.ts: mixed user+group: refs, input-order dedupe, invalid group: rejection, case-insensitive prefix.
  • [P2] refs.ts:427 — input order is now preserved across mixed user/group: refs (['id:30', 'group:100'] resolves to [30, 10, 20], not [10, 20, 30]).
  • [P2] refs.ts:456resolveUserRefs + all resolveGroupRef calls now run concurrently in one Promise.all.
  • [P2] members.ts:64, :189, :255 — independent fetches now run concurrently (resolveChannelRef + resolveChannelMemberRefs; resolveChannelRef + getWorkspaceGroups; channel + session-user + member-refs in sync).
  • [P2] members.ts:69 — dropped redundant dedupe(); resolveChannelMemberRefs already dedupes via a Set.
  • [P2] members.ts:223--ndjson now honours --full the same way --json does (slim default, raw on --full).
  • [P2] members.ts:295 — add and remove run concurrently in sync when both are non-empty.
  • [P2] members.test.ts:60mockGetUserById now returns a tagged sentinel and we assert batch() receives those exact requests (new test batches one getUserById request per channel member).
  • [P2] members.test.ts:61--full is now exercised across add, remove, and the members view.
  • [P2] members.test.ts:234 — added remove --dry-run and remove --json coverage.
  • [P3] members.ts:137 — extracted a logExpansion helper used by both mutate and sync.

Deferred ([P2] members.ts:57): extracting a shared membership-mutation flow between src/commands/groups/members.ts and src/commands/channel/members.ts.

I see the duplication and agree it'll drift. I'm punting it to a follow-up PR for two reasons:

  1. The shared helper would have to touch groups/members.ts, which is outside this PR's scope and out of the existing 700–1000 LOC-per-PR target (the @ScottL guideline from 2026-05-19). Bundling it would put the diff well over.
  2. The two flows have small but real divergences (group expansion + the expandedFrom reporting only matter for channels) that the shared helper would need to abstract cleanly. I'd rather design that with the duplication visible than rush an abstraction.

Happy to open the follow-up immediately after this lands. Let me know if you'd rather I roll it in here.

Copy link
Copy Markdown
Collaborator

@scottlovegrove scottlovegrove left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A couple of big changes required, once they are done, I'll go through the PR again.

Comment thread src/commands/channel/index.ts Outdated
Comment thread src/commands/channel/members.test.ts
Per @scottlovegrove's review feedback. Each subcommand now lives in its
own file, mirroring the existing channel/ folder pattern (list.ts,
threads.ts, helpers.ts):

- src/commands/channel/membership-helpers.ts: shared bits used by add,
  remove, sync, and the members list view (channelUserIds,
  fetchUsersByIds, logExpansion, describeExpansion, groupsFullyInChannel,
  mutateChannelMembership).
- src/commands/channel/members.ts: now only the list handler.
- src/commands/channel/add.ts, remove.ts: thin wrappers around
  mutateChannelMembership.
- src/commands/channel/sync.ts: the sync handler (kept here because of
  the self-removal guard and dry-run-by-default behaviour).
- src/commands/channel/index.ts: imports updated.

Tests split to match:

- members.test.ts: only the list tests.
- add.test.ts, remove.test.ts, sync.test.ts: one file per command, each
  with its own minimal mock setup.

677 tests still passing (43 test files, +3 from the split).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@lmjabreu
Copy link
Copy Markdown
Contributor Author

Done in 0c40d20 (@scottlovegrove).

Code splitsrc/commands/channel/ now follows the same one-file-per-command shape as the rest of the folder:

I considered keeping the shared helpers inside the existing helpers.ts, but that file is narrow (just cursor encode/decode) and the membership bits are a meaningful chunk. Happy to merge if you'd prefer.

Tests split — one file per command:

677 tests still passing (43 test files, +3 from the split). Type-check and lint clean. Ready for your second pass when you've got a moment.

@lmjabreu lmjabreu requested a review from scottlovegrove May 22, 2026 13:05
Copy link
Copy Markdown
Collaborator

@scottlovegrove scottlovegrove left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm still not 100% sold on the API surface of a lot of the commands here.

  • tw channel add - Sounds like a command you'd use to add a new channel, whereas we are using it for adding members
  • tw channel sync - Sync what? It's not immediately apparent what is going on unless you do the help flag. This could even be tw channel members set.
  • tw channel remove - Similar to add, sounds like the command you'd use to delete the channel.

I think a lot of the member-related ones should be sub-commands of the members command, ensures the context is right.

Comment thread src/lib/skills/content.ts Outdated
tw channel add <ref> id:123 --dry-run # Preview the diff
tw channel remove <channel-ref> <ref...> # Remove users and/or group:<ref>
tw channel sync <channel-ref> <ref...> # Replace membership; dry-run by default
tw channel sync <ref> group:Squad --apply # Mutate. Refuses to remove self unless --include-self
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
tw channel sync <ref> group:Squad --apply # Mutate. Refuses to remove self unless --include-self
tw channel sync <channel-ref> group:Squad --apply # Mutate. Refuses to remove self unless --include-self

Let's keep this consistent with the line above

Comment thread package-lock.json
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What changed on this file? Should be nothing, right? Ask your agent to reset this file.

Comment thread src/lib/refs.ts Outdated
Comment on lines +401 to +404
export interface ChannelMemberRefs {
userIds: number[]
expandedFrom: { groupId: number; groupName: string; userIds: number[] }[]
}
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Prefer type instead of interface.

Restructure the membership commands as nested sub-commands of `members`
so the context is explicit, and rename `sync` to `set`:

    tw channel members <ref>              # list (default)
    tw channel members add    <ref> ...
    tw channel members remove <ref> ...
    tw channel members set    <ref> ...   # was: sync

Reasoning: `tw channel add` / `remove` / `sync` sounded like
channel-level operations (creating a channel, deleting it, syncing
something unclear). Nesting under `members` makes intent unambiguous,
and `set` reads as the replacement semantic better than `sync`.

`members` is now a pure command group with `list` as its
`{ isDefault: true }` subcommand, so `tw channel members <ref>`
continues to list (default action) without options bleeding between
the parent and the child mutate subcommands.

Also:

- src/lib/refs.ts: switch `interface ChannelMemberRefs` to `type` per
  repo preference.
- src/lib/skills/content.ts: fix `<ref>` → `<channel-ref>` for
  consistency with the line above it; update the Quick Reference and
  notes to reflect the new command structure. Regenerated SKILL.md.
- package-lock.json: reset to main; the previous diff was an
  unintentional version bump from `npm i` and shouldn't have been
  committed.
- src/commands/channel/sync.ts → set.ts (with handler and option
  type renamed accordingly); sync.test.ts → set.test.ts; tests in
  add/remove/set updated to invoke the new nested paths.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@lmjabreu
Copy link
Copy Markdown
Contributor Author

Addressed in fc5dda2 (@scottlovegrove):

Naming + nesting. Commands now live under members so the noun is explicit:

tw channel members <ref>              # list (default)
tw channel members add    <ref> ...
tw channel members remove <ref> ...
tw channel members set    <ref> ...   # renamed from `sync`

members is now a pure command group; list is its { isDefault: true } subcommand. That keeps tw channel members <ref> working as the no-arg list while preventing the parent's --json from shadowing the mutating subcommands' --json (which surfaced as broken tests during the refactor — easy to miss without the existing coverage).

I went with set over sync because (a) it reads as the replace semantic and (b) it pairs naturally with add / remove. Happy to switch back if you'd rather.

Inline fixes.

  • src/lib/refs.ts:404interface ChannelMemberRefstype ChannelMemberRefs.
  • src/lib/skills/content.ts:231<ref><channel-ref> for consistency; whole Quick Reference + notes paragraph updated to the new structure. SKILL.md regenerated.
  • package-lock.json reset to main. (Your read was right: that diff was leftover from an npm i during the cli-core OAuth dust-up earlier and shouldn't have been committed.)

677 tests still passing, type-check + lint clean. Smoke-tested --help output for tw channel members and tw channel members set --help locally to confirm the new shape renders cleanly.

Re-requesting your review when you've got a moment.

@lmjabreu lmjabreu requested a review from scottlovegrove May 22, 2026 18:33
The previous reset used my local `main` ref which was stale; the resulting
lockfile was inconsistent with package.json's bumped `@doist/cli-core` and
`@doist/twist-sdk` versions, so CI's `npm ci` rejected it across lint,
test, and SKILL.md Sync jobs.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown
Collaborator

@scottlovegrove scottlovegrove left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just one small nit that needs sorting, but other than that, approving.

const workspaceId = await getCurrentWorkspaceId()
const [channel, sessionUser, memberRefs] = await Promise.all([
resolveChannelRef(channelRef, workspaceId),
getSessionUser() as Promise<User>,
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The casting of Promise<User> is not required.

Suggested change
getSessionUser() as Promise<User>,
getSessionUser(),

@lmjabreu lmjabreu merged commit 84ea32b into main May 22, 2026
6 checks passed
@lmjabreu lmjabreu deleted the lmjabreu/focused-boyd-17ebbc branch May 22, 2026 20:56
doist-release-bot Bot added a commit that referenced this pull request May 22, 2026
## [2.43.0](v2.42.0...v2.43.0) (2026-05-22)

### Features

* **channel:** channel membership control (members/add/remove/sync) ([#244](#244)) ([84ea32b](84ea32b))
@doist-release-bot
Copy link
Copy Markdown
Contributor

🎉 This PR is included in version 2.43.0 🎉

The release is available on:

Your semantic-release bot 📦🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants