Welcome to the final chapter of the FeedbackSurvey tutorial!
In Chapter 5: Event-Driven Survey Triggers, we learned how to detect specific moments to ask for feedback. In Chapter 2: Interactive Prompt Views, we built the "Waiver Form" where users click "Yes" to share their data.
But what actually happens when they click "Yes"?
The application has a massive amount of data: chat history, file contents, and error logs. We need to package all of this up, clean it of secrets, and send it to the server.
This is handled by the Transcript Data Submission module.
Imagine your application is a corporate office. When a user agrees to share their session, itβs like theyβve signed a document to mail to headquarters.
We don't just throw the loose papers out the window. We use a Secure Mailroom (submitTranscriptShare).
The main entry point is a single function called submitTranscriptShare. You don't need to worry about HTTP headers or JSON formatting. You just hand it the "pile of papers."
import { submitTranscriptShare } from './submitTranscriptShare';
await submitTranscriptShare(
messages, // The entire chat history
'bad_feedback_survey', // The reason (Trigger)
'unique-session-id-123' // The Ticket Number
);
messages: The list of everything the User and the AI said.trigger: A label explaining why we are sending this (e.g., "User was frustrated", "Memory bug").appearanceId: The unique ID we generated in Chapter 3: Survey Lifecycle State Machine.The function returns a Promise (a receipt).
// The result looks like this:
{
success: true,
transcriptId: "txt_8f9s8d9f" // The ID assigned by the server
}
If the internet is down or the server rejects us, success will be false.
Let's look at what happens inside the mailroom.
First, we need to convert the chat messages into a clean format the server understands. We also try to read the raw log file from the disk.
// inside submitTranscriptShare...
// 1. Clean up the message objects
const transcript = normalizeMessagesForAPI(messages);
// 2. Try to read the raw log file from disk
let rawTranscriptJsonl;
// SAFETY CHECK: Only read if file is small enough!
if (fileSize <= MAX_TRANSCRIPT_READ_BYTES) {
rawTranscriptJsonl = await readFile(transcriptPath, 'utf-8');
}
Why check the size? If a user has been chatting for 5 days straight, the log file might be 500MB. Trying to load that into memory could crash the application. If it's too big, we skip it.
This is the most critical step for user trust. We take the data we prepared and pass it through a "Redactor."
// 3. Create the full data package
const data = {
trigger: trigger,
version: '1.0.0',
transcript: transcript,
rawTranscriptJsonl: rawTranscriptJsonl
};
// 4. Scrub secrets!
// jsonStringify converts the object to text
// redactSensitiveInfo replaces keys with [REDACTED]
const content = redactSensitiveInfo(jsonStringify(data));
redactSensitiveInfo uses Regular Expressions (pattern matching) to find things that look like API keys or passwords and replaces them with ****.
We need permission to speak to the server. We check if our "ID Badge" (OAuth Token) is valid.
// 5. Ensure we are logged in
await checkAndRefreshOAuthTokenIfNeeded();
// 6. Get the headers (The "Stamp")
const authResult = getAuthHeaders();
if (authResult.error) {
return { success: false }; // Abort if not logged in
}
Finally, we use a library called axios (our truck driver) to send the package to the API URL.
// 7. Send the POST request
const response = await axios.post(
'https://api.anthropic.com/.../transcripts',
{ content, appearance_id: appearanceId },
{ headers: authResult.headers }
);
// 8. Check if it arrived safely
if (response.status === 200) {
return { success: true, transcriptId: response.data.id };
}
Let's visualize the journey of a transcript when a user clicks "Yes."
You might notice a reference to subagentTranscripts in the full code.
Sometimes, the main AI assistant spawns "Sub-Agents" (little helper bots) to do specific tasks like searching code. These helpers have their own separate chat logs.
// Collect logs from the little helper bots
const agentIds = extractAgentIdsFromMessages(messages);
const subagentTranscripts = await loadSubagentTranscripts(agentIds);
We bundle these logs into the same envelope so the developers can see the entire picture of what went wrong, not just the main conversation.
Congratulations! You have completed the FeedbackSurvey tutorial.
Let's review the full journey we've built:
You now understand the architecture of a professional-grade CLI survey tool. It balances user experience (not being annoying), legal compliance (asking for permission), and engineering robustness (redaction and safe data handling).
Happy coding!
Generated by Code IQ