Fix #2398: share one synthetic session across slice integration branch creation - #2405
Conversation
…h creation `create_slice_integration_branch` previously made three sequential gateway calls — `fetch_branch`, `get_remote_branch_sha`, and the push — each registering and deleting its own synthetic session. For a 15-slice pipeline that's 45 session lifecycles where one would do. Add an optional keyword-only `bearer_token` to `fetch_branch` and `get_remote_branch_sha`: when supplied, they skip the per-call register/delete and authenticate with the caller's token. Then move the `register_session` in `create_slice_integration_branch` to run before the fetch/ls-remote/push sequence and pass that one token to all three. The session metadata is the strictest of the three (push needs `branch=integration_branch` + `agent_role` for the slice-integration-branch exemption); the fetch and ls-remote endpoints accept any synthetic session, so the extra metadata is harmless there. 15-slice pipelines now register 15 sessions instead of 45. Two new tests in `test_create_slice_integration_branch.py` pin the contract: the same bearer token reaches `fetch_branch`, `get_remote_branch_sha`, and the push, and the session is still cleaned up when the parent is missing on origin (which now runs after register_session, so the cleanup path matters). Closes #2398.
There was a problem hiding this comment.
Reviewed gateway_client.py and test_create_slice_integration_branch.py. Cross-checked the four other call sites of fetch_branch / get_remote_branch_sha (state_store.py:952, routes/pipelines.py:1473/1478/4148/4337) and the gateway side of fetch/ls-remote/push routes. The refactor is correct and well-bounded.
Correctness
- The
owns_session = bearer_token is Noneflag drives both the conditionalregister_sessionand the conditionaldelete_sessioninfinally— every path (success, parent-missing return, mid-flow exception, fetch-best-effort failure) cleans up exactly when it should. fetch_branchandget_remote_branch_shacorrectly skip both register and delete when abearer_tokenis supplied;create_slice_integration_branchowns the session lifecycle for the whole sequence and tears it down in its ownfinally.- The new test
test_session_cleaned_up_when_parent_missingis a good catch — the refactor movesregister_sessionahead of the ls-remote SHA lookup, so the missing-parent return path now has a session to clean up where it previously had none.
Backward compatibility
bearer_token is keyword-only with a None default, and all four existing callers pass everything by keyword (pipeline_id=, repo_path=, args=, mode=), so adding *, bearer_token=None after mode doesn't disturb them. They continue to take the per-call register/delete path — verified.
Security / metadata leakage
The session is registered with branch=integration_branch and agent_role=coder because the push needs both for the _SLICE_INTEGRATION_BRANCH_RE exemption (gateway/gateway.py:1308–1318) and for branch == session.assigned_branch (line 1455–1476). Reusing that session for the fetch + ls-remote is safe: /api/v1/git/fetch (which serves both operation=fetch and operation=ls-remote, line 2854–3057) only enforces @require_session_auth + path/args validation + private-mode policy. It never inspects assigned_branch or agent_role. The PR description's claim that "the extra metadata is harmless for those calls" holds against the route handler.
Session lifetime
DEFAULT_SESSION_TTL_HOURS = 24 (gateway/session_manager.py:243); the worst-case window for fetch (120s timeout) + ls-remote (120s) + push is well under that. Sharing one session across the three calls is not at risk of mid-flow expiry.
Non-blocking notes
gateway_client.py:1956— thefetch_branchdocstring still sayspipeline_id: Pipeline ID (used as container_id for the temp session). Whenbearer_tokenis supplied, that's not literally true — the synthetic container_id from the upstream caller is what's used, andpipeline_idis only consumed for log fields. Worth a one-line clarification so future readers don't misread the contract.gateway_client.py:1957and:2103— themodeparameter is silently ignored whenbearer_tokenis supplied (the session's mode is already established at register time). Not a bug, but a one-line note in the docstring would prevent a future caller passingmode="private"and assuming it's been honored on a borrowed session.test_create_slice_integration_branch.py:385and:419—assert delete_spy.call_args_list == [((("shared-tok"),), {})]is parseable but unusual;from unittest.mock import calland[call("shared-tok")]would read more directly. Style only.
Test coverage
The two new tests pin (a) the one-session-shared invariant including bearer-token forwarding to all three downstream calls, and (b) cleanup on the missing-parent early-return. Both exercise the production code path through create_slice_integration_branch.
LGTM.
— Authored by egg
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
- fetch_branch / get_remote_branch_sha docstrings now note that
pipeline_id is only used for log fields and that mode is ignored
when bearer_token is supplied (the supplied session's mode was
fixed at register time).
- Switch the two delete_spy.call_args_list assertions in
test_create_slice_integration_branch.py to mock.call() so the
expected call list reads as [call("shared-tok")] instead of the
unusual [((("shared-tok"),), {})] tuple form.
All three are non-blocking notes from the egg-reviewer LGTM on #2405.
|
Thanks for the LGTM and the three non-blocking notes — addressed all of them in
— Authored by egg |
This comment has been minimized.
This comment has been minimized.
There was a problem hiding this comment.
Re-review at ebe86c1. Delta since my prior review is purely the three non-blocking notes from the previous LGTM, addressed verbatim:
- Note 1 —
fetch_branchdocstring onpipeline_id(gateway_client.py:1956–1960). Now clarifies thatpipeline_idis used both ascontainer_idfor the temp session and for log fields, and only the latter applies whenbearer_tokenis supplied. Matches the implementation:pipeline_idflows intotemp_container_idinside theif owns_session:branch (skipped on borrowed session) and into the unconditionallogger.info/logger.warningcalls. ✓ - Note 2 —
modeignored on borrowed session (gateway_client.py:1963–1965and:2107–2109). Both docstrings now state thatmodeis ignored whenbearer_tokenis supplied because the supplied session's mode was fixed at register time. Verified:modeis only consumed insideif owns_session:(passed toregister_session); when a token is supplied that branch is skipped. ✓ - Note 3 —
delete_spy.call_args_listusesmock.call()(test_create_slice_integration_branch.py:21,:385,:419). Import added; both assertions rewritten as[call("shared-tok")]/[call("orphan-tok")]. Sanity-checked againstMagicMockdirectly —[call("shared-tok")] == [((("shared-tok"),), {})]isTrue, so the new form is semantically identical and just more readable. ✓
No new logic, no new code paths. Correctness/security/backward-compat analysis from the prior review still holds:
- Single shared synthetic session across fetch + ls-remote + push, owned by
create_slice_integration_branch. - Backward-compat preserved by the keyword-only
bearer_token=Noneparameter; existing callers (state_store.py,routes/pipelines.py) take the original register/delete path. - Session metadata (
branch=integration_branch,agent_role=coder) is the strictest of the three; the/api/v1/git/fetchroute doesn't inspectassigned_branchoragent_role, so reusing the push session for fetch + ls-remote is safe.
LGTM.
— Authored by egg
|
egg review completed. View run logs 3 previous review(s) hidden. |
Summary
orchestrator.gateway_client.create_slice_integration_branchwas making three sequential gateway calls —fetch_branch,get_remote_branch_sha, and the push — each of which registered and deleted its own synthetic session. The reviewer on Fix #2393: push slice integration branch by SHA, not parent ref name #2395 flagged this: for a 15-slice pipeline that's 45 session lifecycles where one would do.bearer_tokentofetch_branchandget_remote_branch_sha: when supplied, they skip the per-call register/delete and authenticate with the caller's token. DefaultNonepreserves every other call site (state_store.py,routes/pipelines.py).create_slice_integration_branch, moveregister_sessionahead of the fetch/ls-remote/push and pass that token to all three. The push's session metadata (branch=integration_branch,agent_role,synthetic=True) is the strictest of the three, and the fetch + ls-remote endpoints accept any synthetic session, so the extra metadata is harmless there.Why follow-up, not in PR #2395
Touches
fetch_branch/get_remote_branch_shasignatures used by other call sites. Perf optimization, not a correctness fix — out of scope for the #2393 hotfix.Test plan
pytest orchestrator/tests/test_create_slice_integration_branch.py— 10 tests pass (8 existing + 2 new)pytest orchestrator/tests/test_state_store.py orchestrator/tests/test_pipelines_api.py orchestrator/tests/test_source_branch.py orchestrator/tests/test_slice_run_loop_integration.py— 259 pass; existing call sites offetch_branch/get_remote_branch_shaunaffected by the new keyword-only parammake test— 3703 passedmake lintCloses #2398.