Repository navigation
fix(deploy): only inject RUNPOD_API_KEY into endpoints that make remote calls - #389
Conversation
OneAsterisk
left a comment
There was a problem hiding this comment.
Claude review, round 1
Verdict: Approved. The deploy fix is correct and the stale-key cleanup is safe; four scanner and duplication issues are worth fixing before or soon after merge.
Approval gate: 0 Critical, 0 High, 7 Medium+Low → approved.
Existing feedback
- No prior reviews or comments. CI: all Quality Gates (3.10 to 3.14), lint, Guard, and Nix flake pass. Build Package skips on PRs.
PR description vs code
| Claim | Status | Where |
|---|---|---|
Deploy pins makes_remote_calls on the resource; flash run keeps the fallback |
✅ | runtime/resource_provisioner.py:206, core/resources/serverless.py:895 |
| Pinned value survives the deploy path (create and update) | ✅ | Every _check_makes_remote_calls call runs on the manifest-built object (serverless.py:1005,1062,1230) |
| Names resolve against "any imports inside the function" | Only modules still in sys.modules; project modules are popped after scan (see Medium 1) |
|
Module references like tasks.square(x) are detected |
One level only; pkg.tasks.square(x) is missed (Medium 2) |
|
| "A function the scanner fails to analyze is treated as needing the key" | True for the endpoint function; a helper that fails to parse returns [] (Medium 3) |
|
| Stale-key cleanup costs "one extra GraphQL read per such endpoint" | ❌ | Two reads (get_endpoint_template_id, get_template), plus a write when a key is found (cli/utils/deployment.py:46,49) |
| Cleanup failures are logged and never fail the deploy | ✅ | deployment.py:81-88, :94-95 |
A key the user set in env is never touched |
✅ | deployment.py:243-245, serverless.py:1065, :1231 |
| 401 hint on a worker without the key | ✅ | endpoint.py:45-61, same request/response so callers' except still matches |
| 39 new test cases | ✅ | Counted, including parametrize cases |
Findings
Medium
- Function-local imports of project modules do not resolve (
scanner.py:494,:504)._import_module_from_filepops each project module fromsys.modulesafter import (scanner.py:264-272), sofrom tasks import square as sq; sq(x)orimport tasks; tasks.square(x)inside a function finds nothing unless another file importedtasksat top level. Relative imports are skipped entirely. Before this PR the miss was masked by the broken fallback; now deploy trusts it. - Nested module attributes are missed (
scanner.py:560). The branch requireschild.valueto be anast.Name, sopkg.tasks.square(x)andconfig.clients.vllm.runsync()are never flagged._resolvealready recurses; drop the guard and label withast.unparse(child). - Helper analysis fails open (
scanner.py:581-582). A helper whose source cannot be fetched or parsed returns[], which contradicts the "errs toward giving the key" design. Return["<unanalyzed>"]instead. - Second copy of the saveTemplate payload builder (
cli/utils/deployment.py:54-64). Same allowed fields,volumeInGb,id, and comment asServerlessResource._build_template_update_payload(serverless.py:853-883). If one gains a field, the other silently drops it on every unchanged redeploy.
Low
- Cleanup runs serially before the timed provisioning block (
deployment.py:263). Two GraphQL calls per unchanged local-only endpoint on every deploy, one after another, outside the 600swait_for. Fine at today's endpoint counts; agatherwould remove the latency. Fix the PR body's "one extra read". - Side effects not in the PR body. The first deploy after upgrade sends
saveTemplateto every unchanged endpoint that still holds the key, which starts a rolling release. ARUNPOD_API_KEYadded by hand in the Runpod console (not in Flashenv) is removed on every deploy. Client-modeimage=endpoints are alwaysmakes_remote_calls: False(manifest.py:362), so a user image that relied on the injected key loses it. Worth a "Things to know" section. - Test gaps on the risky paths.
test_function_local_importsonly usesrunpod_flash,runpod, andjson, which are always loaded, so it cannot catch Medium 1. No test pins a user-setRUNPOD_API_KEYwithmakes_remote_calls=Falseon either update branch inserverless.py.test_errors_never_fail_the_deploycovers only the outerexcept, not a per-endpoint failure followed by the next endpoint.
Nits
cli/commands/deploy.py:183says "added", but the list includes unchanged endpoints that got the key in an earlier deploy. "Endpoints with RUNPOD_API_KEY" is accurate.docs/Cross_Endpoint_Routing.md:194implies every miss surfaces the 401 hint. A missed@Endpoint-to-@Endpointcall goes throughServiceRegistry, which skips the State Manager lookup whenmakes_remote_calls=False(runtime/service_registry.py:184) and never hits_raise_for_status.- Commit subjects are
fix(...); squash so intermediate ones do not land in release notes.
Simplification opportunities
- Reuse
_build_template_update_payloadin_remove_stale_api_key(about 10 lines removed, can be in this PR). - One predicate for "Flash owns this key and the endpoint does not need it", used at
serverless.py:1062,:1229,deployment.py:243,deploy.py:177. Four slightly different forms today. Follow-up is fine.
Verified clean
_makes_remote_callsis set after construction, so CPU/LB validators do not clear it; pickled copies drop it but are only used asselfinupdate.- Manifest aggregation per resource (
manifest.py:595) covers class endpoints and LB routes. @Endpoint(image=...)as a decorator raises (endpoint.py:685), so walkingdecorator_listgives no false positive.runpod_flashdoes not match_is_runpod_module_name.- Cleanup payload sends the same fields the existing update path sends; deleted endpoints return
None. - Tests stub
RUNPOD_ENDPOINT_IDandget_api_keyexplicitly; no ambient env dependency.
Questions for the author
- Are function-local cross-endpoint imports common in real Flash projects? That decides whether Medium 1 should block.
- Is the rolling release on the first post-upgrade deploy acceptable, or should cleanup be opt-in for one release?
Reviewed by Claude (claude-opus-5-5) via the pr-review skill. Tokens: 166 in / 13,517 out / 5,797,746 cache read / 284,779 cache write = 6,096,208. Estimated cost: $3.71 at $4 in / $20 out / $0.20 cache read / $8 cache write per MTok (1-hour cache TTL). Round 1 of this PR. This is automated assistance; a human owns the final call.
There was a problem hiding this comment.
🟡 Changes recommended
Scanner false negatives can withhold required credentials, and shared-template cleanup can remove credentials from other endpoints.
3 open findings
What changed in this PR
Addresses #384 by making deployment follow build-time API-key requirements while preserving the flash run fallback.
Changes:
- Expands remote-call detection and carries its result into provisioning.
- Removes stale keys from local-only endpoints.
- Adds deployment visibility, authentication guidance, documentation, and tests.
| File | Description |
|---|---|
| tests/unit/test_endpoint_client.py | Tests missing-key hints. |
| tests/unit/runtime/test_resource_provisioner.py | Tests manifest-driven injection. |
| tests/unit/resources/test_serverless.py | Tests stale-key removal during updates. |
| tests/unit/core/api/test_runpod_graphql_extended.py | Tests template-ID lookup. |
| tests/unit/cli/utils/test_deployment.py | Tests unchanged-endpoint cleanup. |
| tests/unit/cli/test_deploy.py | Tests key-recipient summaries. |
| tests/unit/cli/commands/build_utils/test_runtime_scanner.py | Expands detection tests. |
| src/runpod_flash/runtime/resource_provisioner.py | Retains the manifest decision. |
| src/runpod_flash/endpoint.py | Explains missing worker credentials. |
| src/runpod_flash/core/resources/serverless.py | Honors detection and removes stale keys. |
| src/runpod_flash/core/api/runpod.py | Adds endpoint template lookup. |
| src/runpod_flash/cli/utils/deployment.py | Cleans unchanged endpoints. |
| src/runpod_flash/cli/commands/deploy.py | Lists automatic key recipients. |
| src/runpod_flash/cli/commands/build_utils/scanner.py | Expands remote-call analysis. |
| docs/Deployment_Architecture.md | Documents injection and cleanup. |
| docs/Cross_Endpoint_Routing.md | Explains detection and remediation. |
🧠 Review effort: Balanced
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
OneAsterisk
left a comment
There was a problem hiding this comment.
Claude review, round 2
Verdict: Approved. Every round 1 finding and every Copilot and @deanq thread is fixed at 3c1265b9; what remains is Low (docs, one test, PR body).
Approval gate: 0 Critical, 0 High, 4 Medium+Low → approved.
Reviewed 551d7557...3c1265b9 (3 commits: 75045515, 69754e0f, 3c1265b9) in full, plus every file a prior finding pointed at. The watcher triggered on 69754e0f; the head has since moved to 3c1265b9, which is what this round covers.
Status of prior findings
| # | Finding | Severity | Status | Evidence |
|---|---|---|---|---|
| R1-1 | Function-local project imports do not resolve | Medium | Fixed | scanner.py:535-596, _CallScan.lookup_module reads project_modules (:807-823); unresolved project imports add <unanalyzed> (:644). Tests use real sibling modules. 75045515 |
| R1-2 | Nested module attributes missed | Medium | Fixed | scanner.py:669-672 handles every Attribute; _resolve falls back to scanned submodules (:454-456). 75045515 |
| R1-3 | Helper analysis fails open | Medium | Fixed | scanner.py:684-687 returns [_UNANALYZED]; test_unparseable_helper_assumes_key_needed. 75045515 |
| R1-4 | Second copy of the saveTemplate payload builder | Medium | Fixed | deployment.py:58-60 calls ServerlessResource._template_update_payload (serverless.py:877). 69754e0f |
| R1-5 | Cleanup serial and outside any timeout | Low | Fixed | One myself.endpoints read, asyncio.gather, 120s wait_for (deployment.py:95-106). PR body updated. 69754e0f |
| R1-6 | Side effects not in the PR body | Low | Not addressed | Body still omits the rolling release on first deploy, removal of a console-added key, and image= endpoints losing the key. |
| R1-7 | Test gaps on risky paths | Low | Partially fixed | Local-import and per-endpoint-failure tests added. Still no test pins a user-set RUNPOD_API_KEY with makes_remote_calls=False on either update branch (test_serverless.py _update only passes LOG_LEVEL). |
| R1-N1 | deploy.py:183 says "added" |
Nit | Not addressed | |
| R1-N2 | Cross_Endpoint_Routing.md:194 implies every miss shows the 401 hint |
Nit | Not addressed | |
| R1-N3 | fix: commit subjects leak into release notes |
Nit | Not addressed | 8 fix(...) commits now. |
Existing feedback
All threads are resolved. Verified each at head:
- Copilot on
deployment.pyshared templates: fixed at69754e0f.template_sharedguards both paths (deployment.py:79,serverless.py:925); lookup failure keeps the key (serverless.py:918-924). #389 (comment) - Copilot on local project imports: fixed at
75045515(see R1-1). #389 (comment) - Copilot on nested attributes: fixed at
75045515(see R1-2). #389 (comment) - @deanq on
__init__.pyhelpers: fixed.project_filesnow built fromall_files(scanner.py:835);test_helper_in_package_init. #389 (comment) - @deanq on project classes and instances: fixed at
75045515and3c1265b9._project_class,_class_targets, and_needs_keyfollow methods, static methods, and nested instance attributes;test_project_classes_and_instancesincludes a pure-class negative case. #389 (comment) - @deanq on
"not found"string matching: fixed by removal. The account-wideendpointsmap treats a deleted endpoint as absent (deployment.py:76-78). #389 (comment)
No CI checks are reported on the branch (gh pr checks: none).
PR description vs code
| Claim | Status | Where |
|---|---|---|
| Local, relative, and package imports resolve, incl. sibling modules | ✅ | scanner.py:535-596 |
pkg.tasks.square(x) detected |
✅ | scanner.py:669-672, test qualified |
Project classes, instances, holder.ep.runsync(x) |
✅ | scanner.py:603-628, :691-713 |
| Unanalyzable helper/class/project import needs the key | ✅ | scanner.py:644, :686, :708 |
One myself.endpoints read plus one template read per endpoint, concurrent, 120s bound |
✅ | deployment.py:95-106 |
| Shared template keeps the key in both paths | ✅ | deployment.py:79, serverless.py:925 |
docs/Cross_Endpoint_Routing.md documents the rules |
Docs predate this round (Low 1) |
Findings
Low
- Docs lag the new rules (
docs/Cross_Endpoint_Routing.md:185,docs/Deployment_Architecture.md:191). Routing docs still say "through a helper function" and omit classes, package paths, local imports, and the unresolved-import rule. Architecture docs say a stale key "is removed on the nextflash deploy" with no shared-template exception. test_slow_api_is_boundedcannot fail (tests/unit/cli/utils/test_deployment.py:1017). Without the bound,hangreturns after 10s,template_ids.getraises insideclean, the outerexceptlogs it, andget_templateis still not awaited. Assert elapsed time or the timeout warning.- R1-6 carried: PR body side effects (see table).
- R1-7 carried: user-set key test on the update branches (see table).
Nits
_should_drop_api_key(serverless.py:906) runs one account-wideendpointsquery per updated endpoint that still holds the key. Fine after the first deploy; worth knowing for large accounts.deployment.py:58calls the privateServerlessResource._template_update_payloadfrom another module. A public name would make the shared use explicit.
Simplification opportunities
- The R1 predicate note still stands: "Flash owns this key and the endpoint does not need it" appears at
serverless.py:1110,:1277,deployment.py:256,deploy.py:177. Follow-up is fine.
Verified clean
_CallScancycle handling: cache seeded with[]per class and helper,seenper_object_needs_keycall, cache fresh per endpoint (scanner.py:755).- Relative imports resolve through the module's
__package__, whichspec_from_file_locationsets; synthetic parent packages are registered before analysis (scanner.py:800). - A failed project import maps to
Noneand yields<unanalyzed>, not a silent miss. _template_update_payloadaddsvalue is not None;model_dump(exclude_none=True)already droppedNone, so the existing update path is unchanged.- Cleanup: deleted endpoint and missing template skip; per-endpoint errors are isolated inside
clean; timeout and lookup errors never fail the deploy.
Questions for the author
- Has this been run against a live deploy yet? The body still says "Not yet verified with a live deploy", and the
myself.endpointschange is new this round.
Reviewed by Claude (claude-opus-5-5) via the pr-review skill. Tokens: 30 in / 13,758 out / 985,372 cache read / 83,903 cache write = 1,083,063. Estimated cost: $2.61 at the Opus 5.5 rate card in the skill's sum_usage.py (cache writes priced at 1-hour TTL). Round 2 of this PR. This is automated assistance; a human owns the final call.
OneAsterisk
left a comment
There was a problem hiding this comment.
Claude review, round 3
Verdict: Approved. The new wrapper and container handling is correct for client Endpoints; one Medium gap remains for wrappers around project helpers.
Approval gate: 0 Critical, 0 High, 6 Medium+Low → approved.
Reviewed 3c1265b9...c0c14f0a (1 commit, 3 files, +88/-2) in full, plus every file an open finding points at.
Status of prior findings
| # | Finding | Severity | Status | Evidence |
|---|---|---|---|---|
| R1-1 to R1-5 | Scanner and cleanup fixes | Medium/Low | Fixed (round 2) | Unchanged at c0c14f0a |
| R1-6 | Side effects not in the PR body | Low | Partially fixed | image= endpoints losing the key is now in the body and Cross_Endpoint_Routing.md:194. The rolling release on first deploy is still unmentioned. |
| R1-7 | No test pins a user-set RUNPOD_API_KEY with makes_remote_calls=False on the update branches |
Low | Not addressed | tests/unit/resources/test_serverless.py unchanged since round 2 |
| R2-1 | Docs lag the new rules | Low | Partially fixed | image= paragraph added. Cross_Endpoint_Routing.md:185 still says "through a helper function" only; Deployment_Architecture.md:191 still has no shared-template exception. |
| R2-2 | test_slow_api_is_bounded cannot fail |
Low | Not addressed | test_deployment.py unchanged |
| R1-N1 | deploy.py:183 says "added" |
Nit | Not addressed | |
| R1-N2 | 401 hint implies every miss shows it | Nit | Not addressed | Cross_Endpoint_Routing.md:196 |
| R1-N3 | fix: commit subjects leak into release notes |
Nit | Not addressed | 9 fix(...) commits |
Existing feedback
- All Copilot and @deanq threads were resolved and verified in round 2. Nothing in this commit reopens them.
- Two round 2 threads stay open (R2-1, R2-2); replies posted.
- No CI checks are reported on the branch.
PR description vs code
| Claim | Status | Where |
|---|---|---|
Bound method around a client Endpoint (run = vllm.runsync) |
✅ | scanner.py:630-631, test bound |
functools.partial around a client Endpoint |
✅ | scanner.py:632-634, test partial |
Container holding a client Endpoint |
✅ | scanner.py:635-640, test container |
lru_cache-style factory |
✅ | _project_function unwraps (scanner.py:502), test cached |
| Partial, container, or wrapper "around any of the above", including a helper | Not for project helper functions (Medium 1) | |
image= endpoints do not receive the key, documented |
✅ | Body "Known limits", Cross_Endpoint_Routing.md:194 |
Findings
Medium
- A partial or container around a project helper is missed (
scanner.py:644)._needs_keyhandles a plain function only through_project_class, which returnsNonefor any function. Socall = functools.partial(call_model, timeout=5)orHANDLERS = {"gen": call_model}, wherecall_modelcallsvllm.runsync, marks the endpoint local-only. The Call path does not catch it either:_unwrapdoes not see through apartial, and aSubscriptcallee does not resolve. Deploy then removes the key and the worker gets a 401. Fix inline.
Low
- R1-6, R1-7, R2-1, R2-2 carried (see table).
Nits
scanner.py:659_unwrapsits next to_unwrap_to_original(scanner.py:152). Both callinspect.unwrap. One helper with the class and exception guard would cover both call sites._MAX_CONTAINER_ITEMScaps each container, not the walk, andseenresets for each AST reference. A nested 1000×1000 constant table referenced N times costs about N million calls. Unlikely in practice.- The
plain_dictnegative case builds a dict literal, which_resolvenever follows. A module-levelCONFIG = {"a": 1}referenced by name would exercise the new container branch's negative path.
Simplification opportunities
- The round 1 predicate note still stands ("Flash owns this key and the endpoint does not need it" in four places). Follow-up is fine.
Verified clean
- Each new test case resolves the way the assertion says.
run_vllmgoes throughMethodType.__self__,CLIENTSthrough the dict branch,callthroughpartial.func.__self__, andclientthrough the Call path after unwrap. _project_functionchecks_is_remotebefore and after unwrap, so an@Endpointfunction is never analyzed as a helper.inspect.unwrapraisesValueErroron cycles and on proxies whose__getattr__fabricates__wrapped__._unwrapcatches it. Classes skip unwrap, so class-level__wrapped__cannot redirect.- Moving the
seencheck ahead of_project_classadds non-project objects toseen. That only skips repeat visits, which return the same answer. strandbytesare excluded from the container branch, so iterating a string cannot recurse.
Questions for the author
- Has this run against a live deploy yet? The body still says "Not yet verified with a live deploy".
Reviewed by Claude (claude-opus-5-5) via the pr-review skill. Tokens: 22 in / 8,857 out / 589,704 cache read / 65,079 cache write = 663,662 (main session, no sub-agents; diff under 300 lines). Estimated cost: $1.89 at the Opus 5.5 rate card in the skill's sum_usage.py (cache writes priced at 1-hour TTL), as of posting. Round 3 of this PR. This is automated assistance; a human owns the final call.
OneAsterisk
left a comment
There was a problem hiding this comment.
Claude review, round 4
Verdict: Approved. This commit closes the docs, test, and message items from earlier rounds; the round 3 Medium (helpers behind a partial or container) is still open.
Approval gate: 0 Critical, 0 High, 2 Medium+Low → approved.
Reviewed c0c14f0a...eeac18c7 (1 commit, 8 files, +44/-14) in full, plus scanner.py for R3-1.
Status of prior findings
| # | Finding | Severity | Status | Evidence |
|---|---|---|---|---|
| R1-1 to R1-5 | Scanner and cleanup fixes | Medium/Low | Fixed (round 2) | Unchanged |
| R1-6 | Side effects not in the PR body | Low | Fixed | Body now says the first deploy rolls local-only endpoints once |
| R1-7 | No test pins a user-set key with makes_remote_calls=False on the update branches |
Low | Partially fixed | New test covers the env-changed branch only. Both cases change env, so serverless.py:1277 stays untested. See inline. |
| R2-1 | Docs lag the new rules | Low | Fixed | Cross_Endpoint_Routing.md:186-192, Deployment_Architecture.md:191 |
| R2-2 | test_slow_api_is_bounded cannot fail |
Low | Fixed | test_deployment.py:1038-1039 asserts elapsed time and the new timeout log |
| R3-1 | Partial or container around a project helper is missed | Medium | Not addressed | scanner.py unchanged since c0c14f0a |
| R1-N1 | deploy.py:183 says "added" |
Nit | Fixed | Now "set by Flash" |
| R1-N2 | 401 hint implies every miss shows it | Nit | Fixed | Cross_Endpoint_Routing.md:198 scopes the message to client Endpoint calls |
| R1-N3 | fix: commit subjects leak into release notes |
Nit | Not addressed | 9 fix(...) commits; fine if squashed with an edited message |
| R3 nits | _unwrap duplication, container walk cost, plain_dict case |
Nit | Not addressed |
Existing feedback
- All Copilot and @deanq threads stay resolved; this commit does not touch their lines.
- R2-1 and R2-2 threads were resolved by the author; verified fixed, replies posted.
- CI: Guard and Pre-check pass. Quality Gates (3.10 to 3.14) and Nix flake were still running at review time.
PR description vs code
| Claim | Status | Where |
|---|---|---|
| Docs describe the detection rules | ✅ | Cross_Endpoint_Routing.md:186-192, including the unresolved-import rule |
| Shared templates and user-set keys are kept | ✅ | Deployment_Architecture.md:191, deployment.py:84-89, serverless.py:1277 |
| Partials and containers around "any of the above" | Not for project helpers (R3-1) | |
| Rolling restart on first deploy after upgrade | ✅ | Body line 20 |
Findings
Medium
- R3-1 carried (
scanner.py:646). Reply in the existing thread.
Low
- R1-7 partially fixed (
test_serverless.py:3615). Inline.
Nits
deployment.py:113dropped!r. An exception with an empty message (for example a bareConnectionError()) now logs nothing after the colon.TimeoutErrorwas the main case and now has its own branch, so this is minor.
Verified clean
except asyncio.TimeoutErrorcatches thewait_fortimeout on 3.10 (where it is not the builtin) and 3.11+ (where it is an alias). It sits beforeexcept Exception, so it wins._template_update_payload→template_update_payload: both callers updated (serverless.py:872,deployment.py:58). No test referenced the old name. The rename removes a cross-module private access.test_slow_api_is_boundednow fails if thewait_forbound is removed:hangsleeps 10s and the test asserts under 2s.test_deploy.py:663matches the new summary string exactly.
Questions for the author
- Has this run against a live deploy yet? The body still says "Not yet verified with a live deploy".
Reviewed by Claude (claude-opus-5-5) via the pr-review skill. Tokens: 18 in / 5,068 out / 445,612 cache read / 52,907 cache write = 503,605 (main session, no sub-agents; diff under 300 lines). Estimated cost: $1.42 at the Opus 5.5 rate card in the skill's sum_usage.py (cache writes priced at 1-hour TTL), as of posting plus this summary. Round 4 of this PR. This is automated assistance; a human owns the final call.
OneAsterisk
left a comment
There was a problem hiding this comment.
Claude review, round 5
Verdict: Approved. Both open items from round 4 are fixed at fbf29985; nothing new above Nit.
Approval gate: 0 Critical, 0 High, 0 Medium+Low → approved.
Reviewed eeac18c7...fbf29985 (2 commits, 3 files, +58/-5) in full, plus the surrounding _needs_key, _project_function, _helper_targets, and the serverless.py update branch.
Status of prior findings
| # | Finding | Severity | Status | Evidence |
|---|---|---|---|---|
| R1-1 to R1-6 | Scanner, cleanup, and PR body fixes | Medium/Low | Fixed (rounds 2 and 4) | Unchanged |
| R1-7 | No test pins a user-set key on the env-unchanged update branch | Low | Fixed | test_serverless.py:3618-3619, fbf29985 |
| R2-1, R2-2 | Docs lag, tautological timeout test | Low | Fixed (round 4) | Unchanged |
| R3-1 | Partial or container around a project helper is missed | Medium | Fixed | scanner.py:644-647, 20cc5c4b |
| R1-N3 | fix: commit subjects leak into release notes |
Nit | Not addressed | 10 fix(...) commits; fine if squashed with an edited message |
| R3 nits, R4 nit | _unwrap duplication, container walk cost, plain_dict case, deployment.py:113 dropped !r |
Nit | Not addressed |
Existing feedback
- All Copilot and @deanq threads stay resolved; these commits do not touch their lines.
- R3-1 and R1-7 threads were resolved by the author. Verified fixed; replies posted.
- CI: Guard and Pre-check pass. Quality Gates (3.10 to 3.14) and Nix flake were in progress at review time.
PR description vs code
| Claim | Status | Where |
|---|---|---|
| Partials and containers around "any of the above", including project helpers | ✅ | scanner.py:644-647; test test_wrapped_and_contained_project_helpers |
A key the user set in env is never touched |
✅ | Both update branches now tested: env-unchanged hits serverless.py:1277, env-changed hits _preserve_platform_env new_keys skip |
Findings
Nits
scanner.py:647A helper called through a module attribute now reports twice (["generate", "clients.generate"]): once from the Call branch (line 704) and once from the Attribute branch (line 713). Only the booleancalls_remote_functionsis consumed (manifest.py:595), so this is cosmetic. The two updated test expectations pin the duplicate; fine to leave.test_serverless.py:3558In the env-unchanged case the returned key is the mock's live"old-key", not"user-key". The test asserts presence only, so it still fails if the line 1277 guard is removed. Assertingenv["RUNPOD_API_KEY"]would be stronger only if the mock's live template held the user value.
Verified clean
- R3-1 path:
call = functools.partial(call_model, ...)→_needs_keypartial branch →call_model→ new_project_functionbranch →_helper_targets→["vllm"]→ True. Same for{"gen": call_model}(container branch) andmap(call_model, xs)(Name branch).PURE = {"double": double}returns[]→ False. - The new branch cannot turn a former True into False. For a plain function
_project_classreturned(None, None)anyway, so the old fall-through was already False. - Cycles:
_helper_targetsseedsscan.cache[id] = []before recursing, so a helper that references itself or a partial of itself terminates. - Unparseable helper behind a partial returns
[_UNANALYZED]→ True, matching the err-toward-the-key rule. - R1-7 test: with old and new env equal,
env_changedis False atserverless.py:1249, so the else branch runs. Removing the"RUNPOD_API_KEY" not in new_config.envguard makes_should_drop_api_keyreturn True (local-only, unshared template) and the key drops, so the test fails as intended. old_env or {...}default keeps the otherTestStaleApiKeyCleanupcases unchanged.
Questions for the author
- Has this run against a live deploy yet? The body still says "Not yet verified with a live deploy".
Reviewed by Claude (claude-opus-5-5) via the pr-review skill. Tokens: 26 in / 6,530 out / 760,723 cache read / 66,110 cache write = 833,389 (main session, no sub-agents; diff under 300 lines). Estimated cost: $1.84 at the Opus 5.5 rate card in the skill's sum_usage.py (cache writes priced at 1-hour TTL), as of posting plus this summary. Round 5 of this PR. This is automated assistance; a human owns the final call.
…te calls flash deploy looked for flash_manifest.json in the wrong place, missed it, and fell back to giving every endpoint the deployer's API key. - carry the manifest's makes_remote_calls onto the deployed resource - detect calls through client-mode Endpoints (id= or image=) at build time - drop a stale RUNPOD_API_KEY from live templates when no longer needed
…od SDK Deploy now trusts the manifest's makes_remote_calls, so a missed detection means a 401 at runtime instead of a silently over-privileged endpoint. Resolve names against the module's globals and flag a function when it: - calls a project helper (in any file) that needs the key - builds a client Endpoint under an aliased import - uses the runpod SDK or reads RUNPOD_API_KEY
Review fixes for the API key scoping change: - detect other @endpoint functions reached through a module (tasks.square), an aliased import, or passed as a value; these were never detected and worked only because every endpoint got the key - analyze helpers only in scanned project files, so library code in an in-project .venv cannot flag every endpoint - treat a function the scanner fails to analyze as needing the key instead of crashing flash build - on a worker without RUNPOD_API_KEY, a 401 from a client Endpoint call explains why the key is missing and how to fix it - list the endpoints that received the key in the deploy summary - document what counts as a remote call
…r gaps Address review findings: - remove a stale RUNPOD_API_KEY from unchanged local-only endpoints during flash deploy; reconciliation skips them, so the update path never ran and upgrading Flash then redeploying left the key in place - resolve imports made inside an endpoint function (aliased Endpoint, runpod SDK, project modules) so those callers keep the key - analyze helpers with a fresh cache per endpoint; a helper cached in the middle of a cycle could hide a remote call from a later endpoint - leave user-managed RUNPOD_API_KEY entries out of the deploy summary
…ale-key cleanup The live API answers an unknown endpoint id with an "Endpoint not found" GraphQL error, which surfaced as a confusing cleanup warning.
…ect classes Cross-call analysis now: - resolves function-local and relative imports against the scanned modules, which the scanner removes from sys.modules after importing them - walks package-qualified references such as pkg.tasks.square(x) - analyzes project classes and instances (methods, static/class methods, instance attributes) referenced by an endpoint - includes package __init__.py files in helper scope - reports <unanalyzed> for helpers whose source cannot be parsed and for project imports that cannot be resolved, so detection errs toward the key
…anup - one myself.endpoints query maps every endpoint to its template, replacing the per-endpoint lookup and its error-message matching for deleted endpoints - a template shared with another endpoint keeps RUNPOD_API_KEY, in both the unchanged-redeploy cleanup and the update path - cleanup runs concurrently under a 120s timeout before provisioning - the live-template payload reuses ServerlessResource's saveTemplate builder
…ctories Also document that Endpoint(image=...) deployments do not receive the key, since Flash does not scan their image.
…ules - document classes, package paths, local imports, wrappers, and the unresolved-import rule; note shared templates and user-set keys - deploy summary says the key was set by Flash, not added - log an explicit message when stale-key cleanup times out - make the shared saveTemplate payload builder public - tests: the cleanup bound actually returns early; a user-set key survives both update branches
fbf2998 to
2832431
Compare
OneAsterisk
left a comment
There was a problem hiding this comment.
Claude review, round 6
Verdict: Approved. The **kwargs change errs toward the key, as the PR intends. One Low: it also matches @Endpoint(..., **cfg) decorators.
Approval gate: 0 Critical, 0 High, 1 Medium+Low → approved.
Reviewed 2832431e...78055475 (1 commit, 2 files, +6/-3) in full, plus _remote_call_targets and _analyze_cross_calls_ast, which call the changed function.
Status of prior findings
| # | Finding | Severity | Status | Evidence |
|---|---|---|---|---|
| R1-1 to R1-7, R2-1, R2-2, R3-1 | Scanner, cleanup, docs, test, and PR body fixes | Medium/Low | Fixed (rounds 2 to 5) | Unchanged by this commit |
| R1-N3 | fix: commit subjects leak into release notes |
Nit | Not addressed | 11 fix(...) commits now. Fine if the squash message is edited |
| R3 to R5 nits | _unwrap duplication, container walk cost, plain_dict case, dropped !r, duplicate helper report, "old-key" assertion |
Nit | Not addressed | Not counted |
Existing feedback
- Copilot and @deanq threads stay resolved. This commit does not touch their lines.
- No open review threads at this head.
- CI: Guard and Pre-check pass. Quality Gates (3.10 to 3.14) and Nix flake are still pending.
PR description vs code
| Claim | Status | Where |
|---|---|---|
Client-mode Endpoint(id=...) / Endpoint(image=...) detected inline and under aliases |
✅ | scanner.py:538, now also **kw |
| Detection errs toward giving the key | ✅ | **kw treated as possibly carrying id or image |
Findings
Low
@Endpoint(name=..., **cfg)decorators now flag their own function (scanner.py:538)._analyze_cross_calls_astpasses the wholeFunctionDef/ClassDefto_remote_call_targets(line 799).ast.walkthere visitsdecorator_list, so the decorator'sEndpoint(...)call reaches the newkw.arg is Nonematch. That decorated endpoint gets reported as["Endpoint"]and keeps the deployer's key, which is the #384 behavior this PR removes. It fails safe, and the summary lists it, so it is Low. The fix: skip decorator calls on the analyzed node. Client mode is never a decorator (endpoint.py:82-89raises). Add a negative test with@Endpoint(name="w", **CFG).
Nits
docs/Cross_Endpoint_Routing.md:189and the PR body list onlyEndpoint(id=...)/Endpoint(image=...). One clause coveringEndpoint(**kwargs)would match the code.
Verified clean
idandimageare keyword-only (endpoint.py:423-442), so positional args cannot carry them. Matchingkw.arg is Nonecovers the only other way to pass them.Endpoint(name="x")inline without**still returns False. Old behavior is unchanged for explicit keywords.- The new parametrized case
**{"id": "abc123"}produces a keyword witharg=None. The test asserts["Endpoint"]and would fail withoutNonein the tuple. - Module-level
ep = Endpoint(**cfg)goes through the live-object check (_is_client_endpoint, line 462), not this AST path, so it is classified by the realis_client.
Questions for the author
- Has this run against a live deploy yet? The body still says "Not yet verified with a live deploy".
Reviewed by Claude (claude-opus-5-5) via the pr-review skill. Tokens: 20 in / 5,213 out / 507,442 cache read / 53,285 cache write = 565,960 (main session, no sub-agents; diff under 300 lines). Estimated cost: $1.45 at the Opus 5.5 rate card in the skill's sum_usage.py (cache writes priced at 1-hour TTL), as of posting plus this summary. Round 6 of this PR. This is automated assistance; a human owns the final call.
Promptless documentation updates
|


Closes #384
flash deploygave every endpoint the deployer'sRUNPOD_API_KEY._check_makes_remote_callssearched./flash_manifest.jsonand/flash_manifest.json, but deploy keeps the manifest at.flash/flash_manifest.json, so the lookup missed and fell back to "needs the key".Changes
Deploy follows the manifest.
create_resource_from_manifeststoresmakes_remote_callson the resource;_check_makes_remote_callsuses it before any file lookup.flash runhas no manifest and keeps the old fallback (fix(run): inject API token for LB cross-endpoint calls #347).Build detection errs toward giving the key. Deploy now trusts the build, so a missed detection is a runtime 401. Names are resolved against the module's globals and any imports inside the function, including relative imports and sibling project modules. A function needs the key when it, directly or through a helper or class in a scanned project file (package
__init__.pyincluded):@Endpointfunction or class, including through a module or package path (tasks.square(x),pkg.tasks.square(x)), an aliased import, or as a value (map(square, xs)). Only direct by-name calls were detected before.Endpoint(id=...)/Endpoint(image=...), at module level, inline, or under an aliased importrunpodSDK or readsRUNPOD_API_KEYclient.generate(x),Helpers.call(x),holder.ep.runsync(x)), or a bound method,functools.partial, container, orlru_cache-style wrapper around any of the aboveA function, helper, or class the scanner cannot analyze, and a project import it cannot resolve, is treated as needing the key.
Stale keys are removed. On update, a live
RUNPOD_API_KEYis no longer carried forward when the endpoint does not make remote calls. Unchanged endpoints skip the update path, so deploy also removes the key from each unchanged local-only endpoint's template. This costs onemyself.endpointsread plus one template read per such endpoint, run concurrently under a 120s bound. Failures are logged and never fail the deploy. A template shared with another endpoint keeps the key in both paths, since that endpoint may call others. A key the user set inenvis never touched.Users can see and fix it. The deploy summary lists the endpoints Flash gave the key. On a worker without the key, a 401 from a client
Endpointcall explains why and what to change.docs/Cross_Endpoint_Routing.mddocuments the rules.Side effects on existing deployments
RUNPOD_API_KEYfrom local-only endpoints. The template env changes, so those endpoints roll their workers once.envis kept.Known limits
Endpointpassed in as an argument, or a hand-built HTTP request that never namesRUNPOD_API_KEY, is not detected. The 401 message points to the fix.Endpoint(image=...)that Flash deploys runs an image Flash does not scan, so it no longer gets the key. If that image calls other endpoints, setRUNPOD_API_KEYin itsenv(documented).envis unchanged still does not inject runtime vars (the fix: resolve .env from CWD and stop _do_deploy env mutation #236 design). On the deploy path this is covered: an endpoint that starts making remote calls getsRUNPOD_API_KEYin itsenv, which counts as an env change.Testing
__init__helpers, unparseable helpers), provisioner, update paths, unchanged-endpoint cleanup (shared templates, timeout, partial failure), 401 hint, and deploy summary.main's scanner marks the module-attribute, alias, and pass-as-value casesfalse, which the first commit alone would have broken.tests/unit/test_load_balancer_sls_stub.pyare order-dependent and also fail onmain.myself.endpoints { id templateId }query was checked read-only against the live API.