Skip to content

fix: make MCP server registration idempotent#25

Merged
donbeave merged 1 commit into
mainfrom
fix/idempotent-mcp-registration
Apr 8, 2026
Merged

fix: make MCP server registration idempotent#25
donbeave merged 1 commit into
mainfrom
fix/idempotent-mcp-registration

Conversation

@donbeave

@donbeave donbeave commented Apr 8, 2026

Copy link
Copy Markdown
Member

Summary

  • Add || true to claude mcp add commands in the entrypoint so they don't abort the script when the MCP server is already registered from a previous run

Root cause

claude mcp add exits non-zero when the server already exists. The .claude directory is volume-mounted and persists between container restarts, so on the second jackin load the tirith registration fails, set -euo pipefail kills the entrypoint, and Claude never launches.

Introduced by #22.

Test plan

  • cargo nextest run — 213 tests pass
  • Run jackin load twice in a row — second run should launch Claude successfully

🤖 Generated with Claude Code

`claude mcp add` exits non-zero when the server already exists. Since
the .claude directory is volume-mounted and persists between runs, the
second `jackin load` would abort the entrypoint at the tirith registration
step, preventing Claude from launching.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@donbeave donbeave merged commit 23f22ae into main Apr 8, 2026
1 check passed
@donbeave donbeave deleted the fix/idempotent-mcp-registration branch April 8, 2026 11:19
donbeave added a commit that referenced this pull request Apr 20, 2026
…ation

fix: make MCP server registration idempotent
donbeave added a commit that referenced this pull request Apr 21, 2026
…ation

fix: make MCP server registration idempotent
donbeave added a commit that referenced this pull request Apr 21, 2026
fix: make MCP server registration idempotent
donbeave added a commit that referenced this pull request Apr 21, 2026
fix: make MCP server registration idempotent
donbeave added a commit that referenced this pull request Apr 21, 2026
fix: make MCP server registration idempotent
donbeave added a commit that referenced this pull request May 7, 2026
fix: make MCP server registration idempotent
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
Co-authored-by: Codex <codex@openai.com>
donbeave added a commit that referenced this pull request May 7, 2026
fix: make MCP server registration idempotent

Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
Co-authored-by: Codex <codex@openai.com>
donbeave added a commit that referenced this pull request May 7, 2026
#25)

`claude mcp add` exits non-zero when the server already exists. Since
the .claude directory is volume-mounted and persists between runs, the
second `jackin load` would abort the entrypoint at the tirith registration
step, preventing Claude from launching.

Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
Co-authored-by: Claude <noreply@anthropic.com>
donbeave added a commit that referenced this pull request May 7, 2026
fix: make MCP server registration idempotent

Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
Co-authored-by: Codex <codex@openai.com>
donbeave added a commit that referenced this pull request May 22, 2026
Address review findings for stale comments, the
`instance_action_accepts_status` negative-match idiom, and two
workflow rough edges.

Comments
- `crates/jackin-container/src/daemon.rs:12` module doc: replace the
  stale "daemon is persistent: does not exit when the last session
  dies" line with the actual behavior (exits when sessions empty so
  the container reaps cleanly), matching memory file
  `container-lifecycle-policy.md`.
- `daemon.rs::ClientFrame::Command`: drop "Phase 3 has no senders
  yet" transitional reference.
- `daemon.rs::handle_attach_client` / `drain_and_exit`: move the
  "Per-client connection handler..." docstring onto
  `handle_attach_client` where it actually applies, and leave the
  Shutdown-and-pause description on `drain_and_exit`.
- `dialog.rs::PALETTE_ITEMS`: drop the "the New agent session entry
  was removed" PR-history sentence; keep the "Next/Previous tab not
  in palette" rationale because that *describes current design*, not
  history.
- `runtime/attach.rs::reconnect_or_create_session`: delete the
  fossilised `TMUX= prevents nested-session warnings` docstring
  (function never set TMUX=, and tmux is gone from the runtime
  entirely) and collapse the redundant `has_sessions`/`let _` pair.
- `src/console/manager/render/list.rs::compute_sidebar_layout`:
  rewrite the misleading "Global mounts header still renders alone"
  comment to describe the actual `show_global`/`show_role_global`
  truth table.

`instance_action_accepts_status` exhaustive grid (#25)
- Convert to a 2D `match (action, status)` with positive matches per
  arm. The previous `!matches!(status, Purged)` shape silently
  flipped half the action × status grid whenever a new
  `InstanceStatus` variant was added — exactly the kind of regression
  the project's single-reviewer staffing rule says compile-time
  exhaustiveness should catch.

Workflow polish (#41)
- `preview.yml`: `gh release delete preview` previously masked all
  errors via `2>/dev/null || true`, so auth / rate-limit failures
  surfaced as a confusing "tag already exists" error from the next
  step. Branch on "release not found" (legit on fresh repo) vs any
  other failure (error out with the captured stderr).
- Same job: add `set -euo pipefail` to both the publish-binary block
  and the release-publish block so a mid-pipeline failure doesn't
  silently continue.

Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
donbeave added a commit that referenced this pull request May 23, 2026
Address review findings for stale comments, the
`instance_action_accepts_status` negative-match idiom, and two
workflow rough edges.

Comments
- `crates/jackin-container/src/daemon.rs:12` module doc: replace the
  stale "daemon is persistent: does not exit when the last session
  dies" line with the actual behavior (exits when sessions empty so
  the container reaps cleanly), matching memory file
  `container-lifecycle-policy.md`.
- `daemon.rs::ClientFrame::Command`: drop "Phase 3 has no senders
  yet" transitional reference.
- `daemon.rs::handle_attach_client` / `drain_and_exit`: move the
  "Per-client connection handler..." docstring onto
  `handle_attach_client` where it actually applies, and leave the
  Shutdown-and-pause description on `drain_and_exit`.
- `dialog.rs::PALETTE_ITEMS`: drop the "the New agent session entry
  was removed" PR-history sentence; keep the "Next/Previous tab not
  in palette" rationale because that *describes current design*, not
  history.
- `runtime/attach.rs::reconnect_or_create_session`: delete the
  fossilised `TMUX= prevents nested-session warnings` docstring
  (function never set TMUX=, and tmux is gone from the runtime
  entirely) and collapse the redundant `has_sessions`/`let _` pair.
- `src/console/manager/render/list.rs::compute_sidebar_layout`:
  rewrite the misleading "Global mounts header still renders alone"
  comment to describe the actual `show_global`/`show_role_global`
  truth table.

`instance_action_accepts_status` exhaustive grid (#25)
- Convert to a 2D `match (action, status)` with positive matches per
  arm. The previous `!matches!(status, Purged)` shape silently
  flipped half the action × status grid whenever a new
  `InstanceStatus` variant was added — exactly the kind of regression
  the project's single-reviewer staffing rule says compile-time
  exhaustiveness should catch.

Workflow polish (#41)
- `preview.yml`: `gh release delete preview` previously masked all
  errors via `2>/dev/null || true`, so auth / rate-limit failures
  surfaced as a confusing "tag already exists" error from the next
  step. Branch on "release not found" (legit on fresh repo) vs any
  other failure (error out with the captured stderr).
- Same job: add `set -euo pipefail` to both the publish-binary block
  and the release-publish block so a mid-pipeline failure doesn't
  silently continue.

Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
donbeave added a commit that referenced this pull request May 23, 2026
Address review findings for stale comments, the
`instance_action_accepts_status` negative-match idiom, and two
workflow rough edges.

Comments
- `crates/jackin-container/src/daemon.rs:12` module doc: replace the
  stale "daemon is persistent: does not exit when the last session
  dies" line with the actual behavior (exits when sessions empty so
  the container reaps cleanly), matching memory file
  `container-lifecycle-policy.md`.
- `daemon.rs::ClientFrame::Command`: drop "Phase 3 has no senders
  yet" transitional reference.
- `daemon.rs::handle_attach_client` / `drain_and_exit`: move the
  "Per-client connection handler..." docstring onto
  `handle_attach_client` where it actually applies, and leave the
  Shutdown-and-pause description on `drain_and_exit`.
- `dialog.rs::PALETTE_ITEMS`: drop the "the New agent session entry
  was removed" PR-history sentence; keep the "Next/Previous tab not
  in palette" rationale because that *describes current design*, not
  history.
- `runtime/attach.rs::reconnect_or_create_session`: delete the
  fossilised `TMUX= prevents nested-session warnings` docstring
  (function never set TMUX=, and tmux is gone from the runtime
  entirely) and collapse the redundant `has_sessions`/`let _` pair.
- `src/console/manager/render/list.rs::compute_sidebar_layout`:
  rewrite the misleading "Global mounts header still renders alone"
  comment to describe the actual `show_global`/`show_role_global`
  truth table.

`instance_action_accepts_status` exhaustive grid (#25)
- Convert to a 2D `match (action, status)` with positive matches per
  arm. The previous `!matches!(status, Purged)` shape silently
  flipped half the action × status grid whenever a new
  `InstanceStatus` variant was added — exactly the kind of regression
  the project's single-reviewer staffing rule says compile-time
  exhaustiveness should catch.

Workflow polish (#41)
- `preview.yml`: `gh release delete preview` previously masked all
  errors via `2>/dev/null || true`, so auth / rate-limit failures
  surfaced as a confusing "tag already exists" error from the next
  step. Branch on "release not found" (legit on fresh repo) vs any
  other failure (error out with the captured stderr).
- Same job: add `set -euo pipefail` to both the publish-binary block
  and the release-publish block so a mid-pipeline failure doesn't
  silently continue.

Co-authored-by: Codex <codex@openai.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
donbeave added a commit that referenced this pull request May 23, 2026
Address review findings for stale comments, the
`instance_action_accepts_status` negative-match idiom, and two
workflow rough edges.

Comments
- `crates/jackin-container/src/daemon.rs:12` module doc: replace the
  stale "daemon is persistent: does not exit when the last session
  dies" line with the actual behavior (exits when sessions empty so
  the container reaps cleanly), matching memory file
  `container-lifecycle-policy.md`.
- `daemon.rs::ClientFrame::Command`: drop "Phase 3 has no senders
  yet" transitional reference.
- `daemon.rs::handle_attach_client` / `drain_and_exit`: move the
  "Per-client connection handler..." docstring onto
  `handle_attach_client` where it actually applies, and leave the
  Shutdown-and-pause description on `drain_and_exit`.
- `dialog.rs::PALETTE_ITEMS`: drop the "the New agent session entry
  was removed" PR-history sentence; keep the "Next/Previous tab not
  in palette" rationale because that *describes current design*, not
  history.
- `runtime/attach.rs::reconnect_or_create_session`: delete the
  fossilised `TMUX= prevents nested-session warnings` docstring
  (function never set TMUX=, and tmux is gone from the runtime
  entirely) and collapse the redundant `has_sessions`/`let _` pair.
- `src/console/manager/render/list.rs::compute_sidebar_layout`:
  rewrite the misleading "Global mounts header still renders alone"
  comment to describe the actual `show_global`/`show_role_global`
  truth table.

`instance_action_accepts_status` exhaustive grid (#25)
- Convert to a 2D `match (action, status)` with positive matches per
  arm. The previous `!matches!(status, Purged)` shape silently
  flipped half the action × status grid whenever a new
  `InstanceStatus` variant was added — exactly the kind of regression
  the project's single-reviewer staffing rule says compile-time
  exhaustiveness should catch.

Workflow polish (#41)
- `preview.yml`: `gh release delete preview` previously masked all
  errors via `2>/dev/null || true`, so auth / rate-limit failures
  surfaced as a confusing "tag already exists" error from the next
  step. Branch on "release not found" (legit on fresh repo) vs any
  other failure (error out with the captured stderr).
- Same job: add `set -euo pipefail` to both the publish-binary block
  and the release-publish block so a mid-pipeline failure doesn't
  silently continue.

Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
Co-authored-by: Codex <codex@openai.com>
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.

1 participant