Engineers love copy‑pasting snippets into ChatGPT, but that manual step breaks automation. Imagine a pipeline where every snippet lives in S3, gets pulled by a Lambda, runs in ChatGPT’s sandbox, and the results are written back automatically. This post shows you how to make that happen today.
Why Store Code Snippets in S3?
Before we write any code, ask yourself: where should a team keep the pieces of logic they want to test on demand?
Think of S3 (Simple Storage Service) as a giant, cheap filing cabinet in the cloud. Each file (an “object”) can be addressed by a bucket name and a key (the path inside the cabinet). Storing snippets there gives you:
- Version‑friendly history – every upload can be a new version, so you can roll back if a test breaks.
- Access control – IAM policies let you say who can read or write, keeping secrets safe.
- Decoupling – your Lambda function only needs the bucket name, not the source code repository.
In plain English: Using S3 turns a random collection of paste‑and‑run files into an organized library that any Lambda can fetch on demand.
Simple analogy
Picture a kitchen where each recipe lives on a separate index card stored in a drawer. Instead of shouting the recipe to a robot chef each time, you pull the card, hand it to the robot, and let it cook. S3 is that drawer; the cards are your snippet files.
Code: create a bucket (run once, e.g., via AWS CLI)
aws s3api create-bucket \
--bucket code-snippets \
--region us-east-1 \
--object-lock-enabled-for-bucket # enable compliance lock if you need audit‑grade immutability
Tip: If you enable Object Lock in Compliance mode, even the root user cannot delete objects. Use it only when you truly need an un‑erasable log.
Setting Up the S3 Bucket and Permissions
Now that we have a bucket, we must let our Lambda read from and write to it. Permissions in AWS are expressed with IAM policies (rules that say “who can do what”).
Why permissions matter
A Lambda that cannot fetch a snippet will error out before it even talks to ChatGPT. Conversely, a Lambda that can write anywhere could accidentally expose secrets. Fine‑grained policies keep the pipeline safe and predictable.
Code: IAM role for the Lambda
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:GetObject",
"s3:PutObject"
],
"Resource": "arn:aws:s3:::code-snippets/*"
},
{
"Effect": "Allow",
"Action": "lambda:InvokeFunction",
"Resource": "*"
}
]
}
- Attach this policy to the role that your Lambda will assume.
Key takeaway: Grant only
GetObjectandPutObjecton the specific bucket; avoid wild‑card bucket names to reduce blast radius.
Gotcha: List operations are eventually consistent
If you rely on ListObjectsV2 to confirm an upload before the Lambda runs, you might see a stale view for a few seconds. A common pattern is to wait for the PutObject promise to resolve (the SDK does that) and then proceed directly to GetObject without a separate list check.
Calling ChatGPT’s Code Interpreter from Node.js
OpenAI’s code interpreter lives inside the regular chat completion endpoint. You send a message with a special system prompt that tells the model “you may run code”. The model then returns a tool call containing the result.
Why use the chat endpoint instead of a separate API
The interpreter is not a standalone service; it is a tool the model can invoke when you enable it. By using the same endpoint, you keep authentication simple (just an API key) and get the same rate‑limit handling you already have for text generation.
Code: minimal wrapper to call the interpreter
import { S3Client, GetObjectCommand, PutObjectCommand } from "@aws-sdk/client-s3";
import fetch from "node-fetch";
import * as fs from "fs";
import path from "path";
// ---------- CONFIG ----------
const OPENAI_API_KEY = process.env.OPENAI_API_KEY!;
const OPENAI_ENDPOINT = "https://api.openai.com/v1/chat/completions";
const BUCKET = "code-snippets";
// ---------------------------
// Helper: read an object from S3 as a string
async function readSnippet(key: string): Promise<string> {
const s3 = new S3Client({});
const resp = await s3.send(new GetObjectCommand({ Bucket: BUCKET, Key: key }));
// resp.Body is a stream; collect it into a string
const chunks: Uint8Array[] = [];
for await (const chunk of resp.Body as any) {
chunks.push(chunk);
}
return Buffer.concat(chunks).toString("utf-8");
}
// Helper: post code to OpenAI and ask the interpreter to run it
async function runInInterpreter(code: string): Promise<string> {
const payload = {
model: "gpt-4o-mini", // model that supports the tool
temperature: 0, // deterministic output
messages: [
{
role: "system",
// The system prompt tells the model it can use the code interpreter tool.
content: "You are a Python execution environment. Use the code interpreter tool to run any code you receive."
},
{
role: "user",
content: `Here is the snippet:\n\`\`\`python\n${code}\n\`\`\`\nRun it and return the stdout.`
}
],
// The `tool_choice` forces the model to use the interpreter if possible.
tool_choice: { type: "function", function: { name: "code_interpreter" } }
};
const resp = await fetch(OPENAI_ENDPOINT, {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${OPENAI_API_KEY}`
},
body: JSON.stringify(payload)
});
if (!resp.ok) {
const err = await resp.text();
throw new Error(`OpenAI error: ${resp.status} ${err}`);
}
const data = await resp.json();
// The result lives in `data.choices[0].message.tool_calls[0].function.arguments`
const toolCall = data.choices[0].message.tool_calls?.[0];
if (!toolCall) throw new Error("No tool call returned");
const result = JSON.parse(toolCall.function.arguments);
return result.output; // OpenAI returns {output: "..."}
}
Tip: The sandbox blocks all network calls and only allows the Python standard library. If your snippet contains
import requests, it will fail with a “module not found” error.
Gotcha: Built‑in stdlib only
The interpreter cannot install third‑party packages. To work around this, keep snippets pure or pre‑package logic into a single file that does not need external dependencies.
Running the Snippet and Capturing Results with Lambda
A Lambda function is a short‑lived piece of code that runs in response to an event (e.g., an S3 upload notification). Here we wire it up so that whenever a new .py file appears, the function pulls it, sends it to the interpreter, and writes the result back to S3.
Why Lambda is a good fit
Lambda gives you automatic scaling (one instance per upload) and pay‑as‑you‑go pricing. It also integrates natively with S3 events, so you don’t need a separate poller.
Code: the Lambda handler
import { S3Event, S3Handler } from "aws-lambda";
import { S3Client, PutObjectCommand } from "@aws-sdk/client-s3";
import { readSnippet, runInInterpreter } from "./interpreter"; // assume same folder
const s3 = new S3Client({}); // uses default credentials from the execution role
export const handler: S3Handler = async (event: S3Event) => {
// 1️⃣ Grab the first record – S3 events can batch, but we process one at a time for clarity
const record = event.Records[0];
const bucket = record.s3.bucket.name;
const key = decodeURIComponent(record.s3.object.key.replace(/\+/g, " "));
// 2️⃣ Only handle .py files – ignore anything else
if (!key.endsWith(".py")) {
console.log(`Skipping non‑Python object: ${key}`);
return;
}
// 3️⃣ Read the snippet content from S3
const code = await readSnippet(key);
console.log(`Fetched snippet ${key} (${code.length} bytes)`);
// 4️⃣ Send to ChatGPT’s interpreter
let output: string;
try {
output = await runInInterpreter(code);
console.log(`Interpreter returned ${output.length} characters`);
} catch (err) {
console.error("Interpreter failed:", err);
output = `Error: ${(err as Error).message}`;
}
// 5️⃣ Write the result back to S3 under a “results/” prefix
const resultKey = `results/${path.basename(key, ".py")}.txt`;
await s3.send(
new PutObjectCommand({
Bucket: bucket,
Key: resultKey,
Body: output,
ContentType: "text/plain"
})
);
console.log(`Wrote result to s3://${bucket}/${resultKey}`);
};
Explanation of each step
- Event parsing – The S3 event tells us which bucket and object triggered the function.
- File filtering – We ignore anything that isn’t a Python file to avoid unnecessary API calls.
-
Fetching –
readSnippet(from the previous section) returns the raw code as a string. -
Calling the interpreter –
runInInterpreterdoes the heavy lifting; any error is caught and turned into a readable message. -
Storing the output – We create a new object in the same bucket under a
results/folder, making it easy to locate all test outcomes.
In plain English: The Lambda acts like a tiny courier: pick up the script, ask ChatGPT to run it, then drop the answer in a designated mailbox.
Gotcha: Lambda layers and ESM in Node 22
If you bundle third‑party libraries as a Lambda layer and use the new ECMAScript module format (import … from …), the runtime can silently ignore the layer. The safest path is to stick with CommonJS (require) until the runtime fully supports ESM in layers.
Putting It All Together: An End‑to‑End Workflow
Let’s walk through the whole process from a developer’s perspective.
-
Developer writes
example.pylocally – a simple script that, say, computes the Fibonacci sequence. - Upload step – a CI job runs a small Node script that pushes the file to S3.
// upload.js – run with `node upload.js`
import { S3Client, PutObjectCommand } from "@aws-sdk/client-s3";
import * as fs from "fs";
const client = new S3Client({});
const bucket = "code-snippets";
const key = "example.py";
const body = fs.readFileSync("example.py");
client.send(
new PutObjectCommand({
Bucket: bucket,
Key: key,
Body: body,
ContentType: "text/x-python"
})
)
.then(() => console.log("Uploaded to S3"))
.catch(err => console.error("Upload failed:", err));
-
S3 event fires – because the bucket has an event notification configured for
s3:ObjectCreated:*that targets the Lambda function we wrote earlier.
{
"LambdaFunctionConfigurations": [
{
"Id": "RunSnippetOnUpload",
"LambdaFunctionArn": "arn:aws:lambda:us-east-1:123456789012:function:RunSnippet",
"Events": ["s3:ObjectCreated:*"],
"Filter": {
"Key": {
"FilterRules": [{ "Name": "suffix", "Value": ".py" }]
}
}
}
]
}
Lambda runs – it fetches
example.py, sends it to the interpreter, and writesresults/example.txt.Result consumption – another downstream job (or a human) can download
results/example.txtto see the output, or a dashboard can list all files underresults/.
Analogy for the whole pipeline
Think of a mail‑order library: you drop a request card (the snippet) into a box (S3). The librarian (Lambda) reads the card, asks a robot (ChatGPT) to perform the task, and places the answer back on a return shelf (the results/ folder). Every step happens automatically, no one has to type anything manually.
Key takeaway: By chaining S3 → Lambda → ChatGPT → S3 you turn an interactive, manual “copy‑paste” into a repeatable CI step that can run on every commit.
The Takeaway
You now have a practical, low‑cost way to execute Python snippets in an isolated, reproducible environment without leaving your codebase.
- Store every testable piece of code in an S3 bucket; it acts as a versioned, permission‑controlled library.
- Give a dedicated Lambda role only
GetObjectandPutObjectrights to keep the surface area small. - Call the code interpreter through the normal
/v1/chat/completionsendpoint, using a system prompt that enables the tool. - Remember the sandbox only knows the Python standard library; external imports will error out.
- Lambda pulls the snippet, runs it, and writes the output back to S3 under a
results/prefix, completing a full automated loop.
With these building blocks you can embed ChatGPT’s interpreter into any CI pipeline, run code reviews, generate quick sanity checks, or build a lightweight “code‑as‑a‑service” layer without managing your own execution environment. Happy automating!
Transparency notice
This article was written with the help of an AI system — Groq (GPT OSS 120B).
Published: 2026-09-21 · Primary focus: S3
All code blocks are intended to be correct and runnable, but please verify them
against the official docs for the tools mentioned before using in production.Find an error? Drop a comment — corrections are always welcome.
Top comments (0)