Repository navigation
Build Lambda functions for their deployed runtime; check Python syntax; skip unchanged functions at dev start - #342
Merged
Conversation
…[skip ci] A function without `runtime` was deployed with the first runtime of its language but built with a separate packaging default, so a Python function ran python3.14 with wheels for 3.12 and failed every request on a native import. The packager now resolves the runtime with the template's `getLambdaRuntime` and the Lambda buildpacks require a version. Python defaults to python3.13: many pinned native packages have no 3.14 wheels, and v3 already used it. Python builds compile the packaged sources, so a syntax error fails packaging with its file and line. Their build image uses the daily pull and local digest pin other generated Dockerfiles use. Dev starts skip functions whose deployed digest matches the build, and a function that fails to build at start is reported as `error` while the session keeps running. Co-Authored-By: Claude Opus 5.5 <[email protected]>
… ci] A dev start whose function failed to build kept the session but returned `ok: true`, so an MCP client tested the function's previous code. `dev --agent` now fails with `DEV_STARTED_WITH_FAILURES` (or `DEV_SESSION_NOT_READY` while a found session still starts), carries the session with `ready: false` in its result data and prints no `AGENT_READY`. The MCP start keeps the session, reports the code, the failed workloads and next actions. The Python build check no longer fails on every packaged file: it follows the entry file's imports through the project, and only those files stop the build. Other files that do not compile produce a warning, and files left out with `excludeFiles` are ignored. A failed language build reports its source files, so dev mode rebuilds the function when any of them changes. Co-Authored-By: Claude Opus 5.5 <[email protected]>
…rallel [skip ci] Each function start clears the packaging manager's jobs when it finishes. A function that finished first removed the source files a failed one had reported, so the watcher followed only its entry file. The files are now taken per job. Co-Authored-By: Claude Opus 5.5 <[email protected]>
Combines #343 (dev runtime robustness) with this branch. A failed function rebuild now also hands the files it read to the watcher, through each attempt of #343's retry, and the not-ready MCP start result works with session selection by stage. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes findings 1, 2 and 8 of the third dev-mode dogfood run (r2p).
Python, Java, Ruby and .NET Lambda functions now build for the runtime they are deployed with. A function without
runtimegot its runtime from the first entry of its language's list in the template, but the packager receivedundefinedand used its own default. A Python function then ran onpython3.14with wheels built for 3.12, and every request failed withRuntime.ImportModuleErroras soon as a native package such as psycopg, pydantic-core or numpy was imported. Ruby (ruby4.0runtime, 3.3 build, so gems sit invendor/bundle/ruby/3.3.0), Java (java25/11) and .NET (dotnet10/8) had the same split. The packager now resolves the runtime withgetLambdaRuntime, the function the template uses. The four Lambda buildpacks require a version and no longer have defaults of their own.fn-deploymentnow passes the configured runtime as well; before, dev rebuilds and deployment scripts built a pinned function with the default version too.Python now defaults to
python3.13, notpython3.14. Native wheels are built per Python minor version, and many pins from before late 2025 have 3.13 wheels but no 3.14 ones:psycopg-binary==3.2.3(the version the dogfood agent wrote, and it has no source distribution either),pydantic-core 2.27.2,numpy 2.2.6. A 3.14 default would turn those runtime import errors into failed installs. 3.13 is also the v3 default runtime, so v3 stacks keep their runtime when they move to v4. Java, Ruby and .NET keep their newest defaults (java25,ruby4.0,dotnet10); their dependencies do not depend on the minor version the way wheels do. The buildpack page and the v3 upgrade guide state the defaults.What existing stacks do on their next deploy: a v4-preview stack with an unpinned Python function changes it from
python3.14topython3.13in place through CloudFormation, and the code is rebuilt for 3.13, because the resolved runtime is part of the digest. Verified live: the stack deployed by origin/main failed every invoke; the redeploy with this branch took 28 s, updated 2 resources in place, and the invoke returned{"python": "3.13", "pq": "binary"}. Pinned functions keep their digest. Unpinned Java and Ruby functions on v3 stacks move tojava25/ruby4.0; that change already came with the v4 runtime list, and now the build follows the runtime.Python builds now check the packaged
.pyfiles with the function's Python version. A small checker runs in the build image before dependencies are installed (bundlers/py/compile-check.ts). It follows the entry file's imports through the project's own files: absolute imports from the build root and from the entry file's directory, and relative imports, including package__init__.pyfiles. It compiles every.pyfile outside virtual environments and hidden directories in memory, so no bytecode is written, and records the result. Only a file the entry file imports, directly or through other project files, fails the build. The error names the file and line, for exampleapi/main.py, line 103: SyntaxError: invalid syntax, sodeploystops early and dev answersREBUILD_FAILED. Live: 0.6 s, and the function kept serving its previous code (r2p gotok: trueafter 5.7 s, then 500s). Any other file that does not compile, such as a script, a test fixture, a template or vendored Python 2 code, produces a warning and the build continues. Files left out withexcludeFilesare ignored. Imports made at run time (importlib) are not followed, so errors there stay warnings. I did not import the handler in the build image to catch missing modules: importing runs module-level code (database connections, environment reads), which would fail good builds.Dev starts:
artifactDigesttag) matches the build is skipped. The build stops after computing the digest, and nothing is uploaded or updated. On a cold start the Python function was skipped 8 ms after the dev stack deploy; Node functions are still redeployed once there because the stack split-bundles them. Warm starts took 5.6 s with all three functions skipped (r2p: 22–31 s).errorwith the reason, and the session keeps running, because the dev stack still holds the function's previous code. The start is not reported as a success.dev --agentexits withDEV_STARTED_WITH_FAILURES, the errors inmessage, and the session indata.resultwithready: falseandfailedWorkloadsalso at the top ofdata; it prints noAGENT_READY. Adev --agentthat finds a running session with a failed workload returns the same code, andDEV_SESSION_NOT_READYwhile that session still starts.CliErrorcan now carrydatainto the JSONL result for this. The MCP start keeps the session, returnsok: falsewith that code and message,data.session,data.ready: false,data.failedWorkloads, andnextActions(fix, then rebuild each failed workload with this session ID, test after it returns ok). Containers and other workloads that fail still stop the session withCLI_DEV_WORKLOAD_FAILED, as before. The agent-mode docs describe both cases.reportSourceFiles). Dev mode records them per job, because the function starts run in parallel and each clears the packaging jobs when it finishes. A JavaScript bundle that fails before it knows its files still leaves only the entry file.public.ecr.awsfor the tag, and a short registry timeout failed starts withDOCKER_COMMAND_FAILED. I did not hard-code a digest in source: that would freeze the image's security updates and need a bump for each release. Java, Ruby and .NET build images do not use the pin yet.Checks:
apps/cli/scripts/packaging-archives/python-lambda-acceptance.ts(test:python-lambda, intest:runtime-acceptances, registered under S21). It packages an unpinned and apython3.12function withpsycopg[binary]==3.2.3through the source CLI, reads the runtime from the template, checks the.soABI tags and invokes both ZIPs in the official Lambda images. Then a syntax error in a module the handler imports must stoppackagewith its file and line. After the fix, a broken script the handler does not import must only be reported. All 9 checks pass (27 s warm). On origin/main, the original 8-check version failed 4 checks with the r2p error.language-lambdas: 17 pass, 0 fail (58 s). New test, with the checker on Python 3.14: a syntax error in a module reached through a package__init__.pystops packaging with file and line, and the failed build still reports that module among its source files. After the fix, a Python 2 script, a tab error in a test fixture and a Jinja template give one warning; the function runs inlambda/python:3.14; the artifact has no bytecode or report file; with those files inexcludeFilesthere is no warning.packaging-images(Docker smoke): 23 real Docker builds passed (176 s).serverless-python-http-dynamodb,serverless-python-sqs-worker): 2/2 passed.rebuild-results.test.ts(real agent server): a running session with a failed build is reported asDEV_STARTED_WITH_FAILURES, and the error'sdatareaches the JSONL result.dev-stop.test.ts(real CLI process against a real agent): a session that still starts gives exit 1,DEV_SESSION_NOT_READYwith the session indata.resultand noAGENT_READY; once it is ready, the same command givesok: true.dev-tool.spec.ts: the MCP start returns the code,ready: false, the failed workloads and next actions, and the session takes a rebuild and a stop.lambda/java:25(2/2).@stacktape/packagingtest 278/278, CLItest:src185 files with 0 failures, CLI and packaging typecheck, oxlint,fmt:check,generate:check(LLM docs regenerated),check:docs,check:workspace,check:patterns,check:deadcode,typecheck:tools,pnpm test:tools85/85.check:duplicatesreports one clone in the private Console checkout (console/ui/e2e/ui-capture), which this change does not touch.stacktape-e2e): the deploy and redeploy above; dev cold start, warm starts, a syntax-error rebuild and a start with a syntax error on a copy of the r2p app. Second round through the real MCP server (stacktape mcpwith the app as its root):startwith a syntax error inapi/helpers.py, whichapi/main.pyimports, returnedok: false,DEV_STARTED_WITH_FAILURES,ready: false, the file and line, and next actions.startthat found the running session returned the same.--watch, fixing onlyapi/helpers.pyrebuiltapi(2.9 s), and status becameready: true.BUILD_CODE_FAILED, because the dev stack itself has to package the function and there is no previous code.Uncertain:
importlib, or found only through asys.pathchange, is not followed, so its syntax error is a warning, not a build failure.net8.0project does not run ondotnet10without roll-forward, with or without this change; I did not test it.