diff --git a/.github/workflows/dev.lock.yml b/.github/workflows/dev.lock.yml
index d6ea4e3142f..fb168fe885b 100644
--- a/.github/workflows/dev.lock.yml
+++ b/.github/workflows/dev.lock.yml
@@ -7316,6 +7316,7 @@ jobs:
           GH_AW_WORKFLOW_NAME: "Dev"
           GH_AW_AGENT_CONCLUSION: ${{ needs.agent.result }}
           GH_AW_DETECTION_CONCLUSION: ${{ needs.detection.result }}
+          GH_AW_SAFE_OUTPUT_MESSAGES: "{\"footer\":\"\\u003e 📝 Poetry generated by [{workflow_name}]({run_url})\",\"footerInstall\":\"\\u003e Want to add poems to your discussions? Install with `gh aw add {workflow_source}`\"}"
         with:
           github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
           script: |
@@ -7858,103 +7859,6 @@ jobs:
         shell: bash
         run: |
           mkdir -p /tmp/gh-aw/scripts
-          cat > /tmp/gh-aw/scripts/generate_footer.cjs << 'EOF_88f9d2d4'
-          // @ts-check
-          /// <reference types="@actions/github-script" />
-          
-          /**
-           * Generates an XML comment marker with agentic workflow metadata for traceability.
-           * This marker enables searching and tracing back items generated by an agentic workflow.
-           *
-           * Note: This function is duplicated in messages_footer.cjs. While normally we would
-           * consolidate to a shared module, importing messages_footer.cjs here would cause the
-           * bundler to inline messages_core.cjs which contains 'GH_AW_SAFE_OUTPUT_MESSAGES:' in
-           * a warning message, breaking tests that check for env var declarations.
-           *
-           * @param {string} workflowName - Name of the workflow
-           * @param {string} runUrl - URL of the workflow run
-           * @returns {string} XML comment marker with workflow metadata
-           */
-          function generateXMLMarker(workflowName, runUrl) {
-            // Read engine metadata from environment variables
-            const engineId = process.env.GH_AW_ENGINE_ID || "";
-            const engineVersion = process.env.GH_AW_ENGINE_VERSION || "";
-            const engineModel = process.env.GH_AW_ENGINE_MODEL || "";
-            const trackerId = process.env.GH_AW_TRACKER_ID || "";
-          
-            // Build the key-value pairs for the marker
-            const parts = [];
-          
-            // Always include agentic-workflow name
-            parts.push(`agentic-workflow: ${workflowName}`);
-          
-            // Add tracker-id if available (for searchability and tracing)
-            if (trackerId) {
-              parts.push(`tracker-id: ${trackerId}`);
-            }
-          
-            // Add engine ID if available
-            if (engineId) {
-              parts.push(`engine: ${engineId}`);
-            }
-          
-            // Add version if available
-            if (engineVersion) {
-              parts.push(`version: ${engineVersion}`);
-            }
-          
-            // Add model if available
-            if (engineModel) {
-              parts.push(`model: ${engineModel}`);
-            }
-          
-            // Always include run URL
-            parts.push(`run: ${runUrl}`);
-          
-            // Return the XML comment marker
-            return `<!-- ${parts.join(", ")} -->`;
-          }
-          
-          /**
-           * Generate footer with AI attribution and workflow installation instructions
-           * @param {string} workflowName - Name of the workflow
-           * @param {string} runUrl - URL of the workflow run
-           * @param {string} workflowSource - Source of the workflow (owner/repo/path@ref)
-           * @param {string} workflowSourceURL - GitHub URL for the workflow source
-           * @param {number|undefined} triggeringIssueNumber - Issue number that triggered this workflow
-           * @param {number|undefined} triggeringPRNumber - Pull request number that triggered this workflow
-           * @param {number|undefined} triggeringDiscussionNumber - Discussion number that triggered this workflow
-           * @returns {string} Footer text
-           */
-          function generateFooter(workflowName, runUrl, workflowSource, workflowSourceURL, triggeringIssueNumber, triggeringPRNumber, triggeringDiscussionNumber) {
-            let footer = `\n\n> AI generated by [${workflowName}](${runUrl})`;
-          
-            // Add reference to triggering issue/PR/discussion if available
-            if (triggeringIssueNumber) {
-              footer += ` for #${triggeringIssueNumber}`;
-            } else if (triggeringPRNumber) {
-              footer += ` for #${triggeringPRNumber}`;
-            } else if (triggeringDiscussionNumber) {
-              footer += ` for discussion #${triggeringDiscussionNumber}`;
-            }
-          
-            if (workflowSource && workflowSourceURL) {
-              footer += `\n>\n> To add this workflow in your repository, run \`gh aw add ${workflowSource}\`. See [usage guide](https://githubnext.github.io/gh-aw/tools/cli/).`;
-            }
-          
-            // Add XML comment marker for traceability
-            footer += "\n\n" + generateXMLMarker(workflowName, runUrl);
-          
-            footer += "\n";
-            return footer;
-          }
-          
-          module.exports = {
-            generateFooter,
-            generateXMLMarker,
-          };
-          
-          EOF_88f9d2d4
           cat > /tmp/gh-aw/scripts/load_agent_output.cjs << 'EOF_b93f537f'
           // @ts-check
           /// <reference types="@actions/github-script" />
@@ -8048,6 +7952,274 @@ jobs:
           module.exports = { loadAgentOutput, truncateForLogging, MAX_LOG_CONTENT_LENGTH };
           
           EOF_b93f537f
+          cat > /tmp/gh-aw/scripts/messages_core.cjs << 'EOF_6cdb27e0'
+          // @ts-check
+          /// <reference types="@actions/github-script" />
+          
+          /**
+           * Core Message Utilities Module
+           *
+           * This module provides shared utilities for message template processing.
+           * It includes configuration parsing and template rendering functions.
+           *
+           * Supported placeholders:
+           * - {workflow_name} - Name of the workflow
+           * - {run_url} - URL to the workflow run
+           * - {workflow_source} - Source specification (owner/repo/path@ref)
+           * - {workflow_source_url} - GitHub URL for the workflow source
+           * - {triggering_number} - Issue/PR/Discussion number that triggered this workflow
+           * - {operation} - Operation name (for staged mode titles/descriptions)
+           * - {event_type} - Event type description (for run-started messages)
+           * - {status} - Workflow status text (for run-failure messages)
+           *
+           * Both camelCase and snake_case placeholder formats are supported.
+           */
+          
+          /**
+           * @typedef {Object} SafeOutputMessages
+           * @property {string} [footer] - Custom footer message template
+           * @property {string} [footerInstall] - Custom installation instructions template
+           * @property {string} [stagedTitle] - Custom staged mode title template
+           * @property {string} [stagedDescription] - Custom staged mode description template
+           * @property {string} [runStarted] - Custom workflow activation message template
+           * @property {string} [runSuccess] - Custom workflow success message template
+           * @property {string} [runFailure] - Custom workflow failure message template
+           * @property {string} [detectionFailure] - Custom detection job failure message template
+           * @property {string} [closeOlderDiscussion] - Custom message for closing older discussions as outdated
+           */
+          
+          /**
+           * Get the safe-output messages configuration from environment variable.
+           * @returns {SafeOutputMessages|null} Parsed messages config or null if not set
+           */
+          function getMessages() {
+            const messagesEnv = process.env.GH_AW_SAFE_OUTPUT_MESSAGES;
+            if (!messagesEnv) {
+              return null;
+            }
+          
+            try {
+              // Parse JSON with camelCase keys from Go struct (using json struct tags)
+              return JSON.parse(messagesEnv);
+            } catch (error) {
+              core.warning(`Failed to parse GH_AW_SAFE_OUTPUT_MESSAGES: ${error instanceof Error ? error.message : String(error)}`);
+              return null;
+            }
+          }
+          
+          /**
+           * Replace placeholders in a template string with values from context.
+           * Supports {key} syntax for placeholder replacement.
+           * @param {string} template - Template string with {key} placeholders
+           * @param {Record<string, string|number|undefined>} context - Key-value pairs for replacement
+           * @returns {string} Template with placeholders replaced
+           */
+          function renderTemplate(template, context) {
+            return template.replace(/\{(\w+)\}/g, (match, key) => {
+              const value = context[key];
+              return value !== undefined && value !== null ? String(value) : match;
+            });
+          }
+          
+          /**
+           * Convert context object keys to snake_case for template rendering
+           * @param {Record<string, any>} obj - Object with camelCase keys
+           * @returns {Record<string, any>} Object with snake_case keys
+           */
+          function toSnakeCase(obj) {
+            /** @type {Record<string, any>} */
+            const result = {};
+            for (const [key, value] of Object.entries(obj)) {
+              // Convert camelCase to snake_case
+              const snakeKey = key.replace(/([A-Z])/g, "_$1").toLowerCase();
+              result[snakeKey] = value;
+              // Also keep original key for backwards compatibility
+              result[key] = value;
+            }
+            return result;
+          }
+          
+          module.exports = {
+            getMessages,
+            renderTemplate,
+            toSnakeCase,
+          };
+          
+          EOF_6cdb27e0
+          cat > /tmp/gh-aw/scripts/messages_footer.cjs << 'EOF_c14886c6'
+          // @ts-check
+          /// <reference types="@actions/github-script" />
+          
+          /**
+           * Footer Message Module
+           *
+           * This module provides footer and installation instructions generation
+           * for safe-output workflows.
+           */
+          
+          const { getMessages, renderTemplate, toSnakeCase } = require('/tmp/gh-aw/scripts/messages_core.cjs');
+          
+          /**
+           * @typedef {Object} FooterContext
+           * @property {string} workflowName - Name of the workflow
+           * @property {string} runUrl - URL of the workflow run
+           * @property {string} [workflowSource] - Source of the workflow (owner/repo/path@ref)
+           * @property {string} [workflowSourceUrl] - GitHub URL for the workflow source
+           * @property {number|string} [triggeringNumber] - Issue, PR, or discussion number that triggered this workflow
+           */
+          
+          /**
+           * Get the footer message, using custom template if configured.
+           * @param {FooterContext} ctx - Context for footer generation
+           * @returns {string} Footer message
+           */
+          function getFooterMessage(ctx) {
+            const messages = getMessages();
+          
+            // Create context with both camelCase and snake_case keys
+            const templateContext = toSnakeCase(ctx);
+          
+            // Default footer template - pirate themed! 🏴‍☠️
+            const defaultFooter = "> Ahoy! This treasure was crafted by [🏴‍☠️ {workflow_name}]({run_url})";
+          
+            // Use custom footer if configured
+            let footer = messages?.footer ? renderTemplate(messages.footer, templateContext) : renderTemplate(defaultFooter, templateContext);
+          
+            // Add triggering reference if available
+            if (ctx.triggeringNumber) {
+              footer += ` fer issue #{triggering_number} 🗺️`.replace("{triggering_number}", String(ctx.triggeringNumber));
+            }
+          
+            return footer;
+          }
+          
+          /**
+           * Get the footer installation instructions, using custom template if configured.
+           * @param {FooterContext} ctx - Context for footer generation
+           * @returns {string} Footer installation message or empty string if no source
+           */
+          function getFooterInstallMessage(ctx) {
+            if (!ctx.workflowSource || !ctx.workflowSourceUrl) {
+              return "";
+            }
+          
+            const messages = getMessages();
+          
+            // Create context with both camelCase and snake_case keys
+            const templateContext = toSnakeCase(ctx);
+          
+            // Default installation template - pirate themed! 🏴‍☠️
+            const defaultInstall = "> Arr! To plunder this workflow fer yer own ship, run `gh aw add {workflow_source}`. Chart yer course at [🦜 {workflow_source_url}]({workflow_source_url})!";
+          
+            // Use custom installation message if configured
+            return messages?.footerInstall ? renderTemplate(messages.footerInstall, templateContext) : renderTemplate(defaultInstall, templateContext);
+          }
+          
+          /**
+           * Generates an XML comment marker with agentic workflow metadata for traceability.
+           * This marker enables searching and tracing back items generated by an agentic workflow.
+           *
+           * The marker format is:
+           * <!-- agentic-workflow: workflow-name, engine: copilot, version: 1.0.0, model: gpt-5, run: https://github.com/... -->
+           *
+           * @param {string} workflowName - Name of the workflow
+           * @param {string} runUrl - URL of the workflow run
+           * @returns {string} XML comment marker with workflow metadata
+           */
+          function generateXMLMarker(workflowName, runUrl) {
+            // Read engine metadata from environment variables
+            const engineId = process.env.GH_AW_ENGINE_ID || "";
+            const engineVersion = process.env.GH_AW_ENGINE_VERSION || "";
+            const engineModel = process.env.GH_AW_ENGINE_MODEL || "";
+            const trackerId = process.env.GH_AW_TRACKER_ID || "";
+          
+            // Build the key-value pairs for the marker
+            const parts = [];
+          
+            // Always include agentic-workflow name
+            parts.push(`agentic-workflow: ${workflowName}`);
+          
+            // Add tracker-id if available (for searchability and tracing)
+            if (trackerId) {
+              parts.push(`tracker-id: ${trackerId}`);
+            }
+          
+            // Add engine ID if available
+            if (engineId) {
+              parts.push(`engine: ${engineId}`);
+            }
+          
+            // Add version if available
+            if (engineVersion) {
+              parts.push(`version: ${engineVersion}`);
+            }
+          
+            // Add model if available
+            if (engineModel) {
+              parts.push(`model: ${engineModel}`);
+            }
+          
+            // Always include run URL
+            parts.push(`run: ${runUrl}`);
+          
+            // Return the XML comment marker
+            return `<!-- ${parts.join(", ")} -->`;
+          }
+          
+          /**
+           * Generate the complete footer with AI attribution and optional installation instructions.
+           * This is a drop-in replacement for the original generateFooter function.
+           * @param {string} workflowName - Name of the workflow
+           * @param {string} runUrl - URL of the workflow run
+           * @param {string} workflowSource - Source of the workflow (owner/repo/path@ref)
+           * @param {string} workflowSourceURL - GitHub URL for the workflow source
+           * @param {number|undefined} triggeringIssueNumber - Issue number that triggered this workflow
+           * @param {number|undefined} triggeringPRNumber - Pull request number that triggered this workflow
+           * @param {number|undefined} triggeringDiscussionNumber - Discussion number that triggered this workflow
+           * @returns {string} Complete footer text
+           */
+          function generateFooterWithMessages(workflowName, runUrl, workflowSource, workflowSourceURL, triggeringIssueNumber, triggeringPRNumber, triggeringDiscussionNumber) {
+            // Determine triggering number (issue takes precedence, then PR, then discussion)
+            let triggeringNumber;
+            if (triggeringIssueNumber) {
+              triggeringNumber = triggeringIssueNumber;
+            } else if (triggeringPRNumber) {
+              triggeringNumber = triggeringPRNumber;
+            } else if (triggeringDiscussionNumber) {
+              triggeringNumber = `discussion #${triggeringDiscussionNumber}`;
+            }
+          
+            const ctx = {
+              workflowName,
+              runUrl,
+              workflowSource,
+              workflowSourceUrl: workflowSourceURL,
+              triggeringNumber,
+            };
+          
+            let footer = "\n\n" + getFooterMessage(ctx);
+          
+            // Add installation instructions if source is available
+            const installMessage = getFooterInstallMessage(ctx);
+            if (installMessage) {
+              footer += "\n>\n" + installMessage;
+            }
+          
+            // Add XML comment marker for traceability
+            footer += "\n\n" + generateXMLMarker(workflowName, runUrl);
+          
+            footer += "\n";
+            return footer;
+          }
+          
+          module.exports = {
+            getFooterMessage,
+            getFooterInstallMessage,
+            generateFooterWithMessages,
+            generateXMLMarker,
+          };
+          
+          EOF_c14886c6
           cat > /tmp/gh-aw/scripts/remove_duplicate_title.cjs << 'EOF_bb4a8126'
           // @ts-check
           /**
@@ -8594,6 +8766,7 @@ jobs:
           GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }}
           GH_AW_WORKFLOW_NAME: "Dev"
           GH_AW_ENGINE_ID: "copilot"
+          GH_AW_SAFE_OUTPUT_MESSAGES: "{\"footer\":\"\\u003e 📝 Poetry generated by [{workflow_name}]({run_url})\",\"footerInstall\":\"\\u003e Want to add poems to your discussions? Install with `gh aw add {workflow_source}`\"}"
           GH_AW_UPDATE_TARGET: "*"
           GH_AW_UPDATE_BODY: "true"
         with:
@@ -8606,7 +8779,7 @@ jobs:
             globalThis.io = io;
             const { runUpdateWorkflow, createRenderStagedItem, createGetSummaryLine } = require('/tmp/gh-aw/scripts/update_runner.cjs');
             const { isDiscussionContext, getDiscussionNumber } = require('/tmp/gh-aw/scripts/update_context_helpers.cjs');
-            const { generateFooter } = require('/tmp/gh-aw/scripts/generate_footer.cjs');
+            const { generateFooterWithMessages } = require('/tmp/gh-aw/scripts/messages_footer.cjs');
             const renderStagedItem = createRenderStagedItem({
               entityName: "Discussion",
               numberField: "discussion_number",
@@ -8650,7 +8823,7 @@ jobs:
                 const triggeringIssueNumber = context.payload.issue?.number;
                 const triggeringPRNumber = context.payload.pull_request?.number;
                 const triggeringDiscussionNumber = context.payload.discussion?.number;
-                const footer = generateFooter(workflowName, runUrl, workflowSource, workflowSourceURL, triggeringIssueNumber, triggeringPRNumber, triggeringDiscussionNumber);
+                const footer = generateFooterWithMessages(workflowName, runUrl, workflowSource, workflowSourceURL, triggeringIssueNumber, triggeringPRNumber, triggeringDiscussionNumber);
                 fieldsToUpdate.body = fieldsToUpdate.body + footer;
               }
               const mutationFields = [];
diff --git a/.github/workflows/dev.md b/.github/workflows/dev.md
index 450ee15475b..3301b901084 100644
--- a/.github/workflows/dev.md
+++ b/.github/workflows/dev.md
@@ -20,6 +20,9 @@ safe-outputs:
   update-discussion:
     target: "*"
     body:
+  messages:
+    footer: "> 📝 Poetry generated by [{workflow_name}]({run_url})"
+    footer-install: "> Want to add poems to your discussions? Install with `gh aw add {workflow_source}`"
 ---
 
 Find the latest discussion in this repository and update its body by appending a short, creative poem about GitHub Agentic Workflows.
diff --git a/docs/src/content/docs/reference/frontmatter-full.md b/docs/src/content/docs/reference/frontmatter-full.md
index b5aade72c47..ac95c1ba104 100644
--- a/docs/src/content/docs/reference/frontmatter-full.md
+++ b/docs/src/content/docs/reference/frontmatter-full.md
@@ -1925,6 +1925,42 @@ safe-outputs:
   # (optional)
   # This field supports multiple formats (oneOf):
 
+  # Option 1: Configuration for updating GitHub discussions from agentic workflow
+  # output
+  update-discussion:
+    # Target for updates: 'triggering' (default), '*' (any discussion), or explicit
+    # discussion number
+    # (optional)
+    target: "example-value"
+
+    # Allow updating discussion title - presence of key indicates field can be updated
+    # (optional)
+    title: null
+
+    # Allow updating discussion body - presence of key indicates field can be updated
+    # (optional)
+    body: null
+
+    # Maximum number of discussions to update (default: 1)
+    # (optional)
+    max: 1
+
+    # Target repository in format 'owner/repo' for cross-repository discussion
+    # updates. Takes precedence over trial target repo settings.
+    # (optional)
+    target-repo: "example-value"
+
+    # GitHub token to use for this specific output type. Overrides global github-token
+    # if specified.
+    # (optional)
+    github-token: "${{ secrets.GITHUB_TOKEN }}"
+
+  # Option 2: Enable discussion updating with default configuration
+  update-discussion: null
+
+  # (optional)
+  # This field supports multiple formats (oneOf):
+
   # Option 1: Configuration for closing GitHub issues with comment from agentic
   # workflow output
   close-issue:
@@ -2863,10 +2899,7 @@ strict: true
 # (JavaScript), 'run' (shell), or 'py' (Python) must be specified per tool.
 # (optional)
 safe-inputs:
-  # Transport mode for the safe-inputs MCP server. Only 'http' mode is supported,
-  # which starts the server as a separate step.
-  # (optional)
-  mode: "http"
+
 
 # Runtime environment version overrides. Allows customizing runtime versions
 # (e.g., Node.js, Python) or defining new runtimes. Runtimes from imported shared
diff --git a/pkg/cli/fix_command_test.go b/pkg/cli/fix_command_test.go
index 7e75b112b0b..2a34cd4be6f 100644
--- a/pkg/cli/fix_command_test.go
+++ b/pkg/cli/fix_command_test.go
@@ -542,12 +542,12 @@ This is a test workflow with slash command.
 }
 
 func TestFixCommand_SafeInputsModeRemoval(t *testing.T) {
-// Create a temporary directory for test files
-tmpDir := t.TempDir()
-workflowFile := filepath.Join(tmpDir, "test-workflow.md")
+	// Create a temporary directory for test files
+	tmpDir := t.TempDir()
+	workflowFile := filepath.Join(tmpDir, "test-workflow.md")
 
-// Create a workflow with deprecated safe-inputs.mode field
-content := `---
+	// Create a workflow with deprecated safe-inputs.mode field
+	content := `---
 on: workflow_dispatch
 engine: copilot
 safe-inputs:
@@ -563,51 +563,51 @@ safe-inputs:
 This is a test workflow with safe-inputs mode field.
 `
 
-if err := os.WriteFile(workflowFile, []byte(content), 0644); err != nil {
-t.Fatalf("Failed to create test file: %v", err)
-}
+	if err := os.WriteFile(workflowFile, []byte(content), 0644); err != nil {
+		t.Fatalf("Failed to create test file: %v", err)
+	}
 
-// Get the safe-inputs mode removal codemod
-modeCodemod := getCodemodByID("safe-inputs-mode-removal")
-if modeCodemod == nil {
-t.Fatal("safe-inputs-mode-removal codemod not found")
-}
+	// Get the safe-inputs mode removal codemod
+	modeCodemod := getCodemodByID("safe-inputs-mode-removal")
+	if modeCodemod == nil {
+		t.Fatal("safe-inputs-mode-removal codemod not found")
+	}
 
-// Process the file
-fixed, err := processWorkflowFile(workflowFile, []Codemod{*modeCodemod}, true, false)
-if err != nil {
-t.Fatalf("Failed to process workflow file: %v", err)
-}
+	// Process the file
+	fixed, err := processWorkflowFile(workflowFile, []Codemod{*modeCodemod}, true, false)
+	if err != nil {
+		t.Fatalf("Failed to process workflow file: %v", err)
+	}
 
-if !fixed {
-t.Error("Expected file to be fixed, but no changes were made")
-}
+	if !fixed {
+		t.Error("Expected file to be fixed, but no changes were made")
+	}
 
-// Read the updated content
-updatedContent, err := os.ReadFile(workflowFile)
-if err != nil {
-t.Fatalf("Failed to read updated file: %v", err)
-}
+	// Read the updated content
+	updatedContent, err := os.ReadFile(workflowFile)
+	if err != nil {
+		t.Fatalf("Failed to read updated file: %v", err)
+	}
 
-updatedStr := string(updatedContent)
+	updatedStr := string(updatedContent)
 
-t.Logf("Updated content:\n%s", updatedStr)
+	t.Logf("Updated content:\n%s", updatedStr)
 
-// Verify the change - mode field should be removed
-if strings.Contains(updatedStr, "mode:") {
-t.Errorf("Expected mode field to be removed, but it still exists:\n%s", updatedStr)
-}
+	// Verify the change - mode field should be removed
+	if strings.Contains(updatedStr, "mode:") {
+		t.Errorf("Expected mode field to be removed, but it still exists:\n%s", updatedStr)
+	}
 
-// Verify safe-inputs block and test-tool are preserved
-if !strings.Contains(updatedStr, "safe-inputs:") {
-t.Error("Expected safe-inputs block to be preserved")
-}
+	// Verify safe-inputs block and test-tool are preserved
+	if !strings.Contains(updatedStr, "safe-inputs:") {
+		t.Error("Expected safe-inputs block to be preserved")
+	}
 
-if !strings.Contains(updatedStr, "test-tool:") {
-t.Error("Expected test-tool to be preserved")
-}
+	if !strings.Contains(updatedStr, "test-tool:") {
+		t.Error("Expected test-tool to be preserved")
+	}
 
-if !strings.Contains(updatedStr, "description: Test tool") {
-t.Error("Expected test-tool description to be preserved")
-}
+	if !strings.Contains(updatedStr, "description: Test tool") {
+		t.Error("Expected test-tool description to be preserved")
+	}
 }
diff --git a/pkg/workflow/js/update_discussion.cjs b/pkg/workflow/js/update_discussion.cjs
index 665b0fefff0..c8432e9f94f 100644
--- a/pkg/workflow/js/update_discussion.cjs
+++ b/pkg/workflow/js/update_discussion.cjs
@@ -3,7 +3,7 @@
 
 const { runUpdateWorkflow, createRenderStagedItem, createGetSummaryLine } = require("./update_runner.cjs");
 const { isDiscussionContext, getDiscussionNumber } = require("./update_context_helpers.cjs");
-const { generateFooter } = require("./generate_footer.cjs");
+const { generateFooterWithMessages } = require("./messages_footer.cjs");
 
 // Use shared helper for staged preview rendering
 const renderStagedItem = createRenderStagedItem({
@@ -72,7 +72,7 @@ async function executeDiscussionUpdate(github, context, discussionNumber, update
     const triggeringDiscussionNumber = context.payload.discussion?.number;
 
     // Append footer to the body
-    const footer = generateFooter(workflowName, runUrl, workflowSource, workflowSourceURL, triggeringIssueNumber, triggeringPRNumber, triggeringDiscussionNumber);
+    const footer = generateFooterWithMessages(workflowName, runUrl, workflowSource, workflowSourceURL, triggeringIssueNumber, triggeringPRNumber, triggeringDiscussionNumber);
     fieldsToUpdate.body = fieldsToUpdate.body + footer;
   }
 
diff --git a/pkg/workflow/js/update_discussion.test.cjs b/pkg/workflow/js/update_discussion.test.cjs
index 29948b1aaea..518469fac05 100644
--- a/pkg/workflow/js/update_discussion.test.cjs
+++ b/pkg/workflow/js/update_discussion.test.cjs
@@ -291,4 +291,53 @@ describe("update_discussion.cjs", () => {
     expect(mockCore.info).toHaveBeenCalledWith("No valid updates to apply for this item");
     expect(mockGithub.graphql).not.toHaveBeenCalled();
   });
+
+  it("should use custom footer message when configured", async () => {
+    setAgentOutput({
+      items: [{ type: "update_discussion", body: "New discussion body" }],
+    });
+    process.env.GH_AW_UPDATE_BODY = "true";
+    process.env.GH_AW_WORKFLOW_NAME = "Custom Workflow";
+    process.env.GH_AW_SAFE_OUTPUT_MESSAGES = JSON.stringify({
+      footer: "> Custom footer by [{workflow_name}]({run_url})",
+    });
+    global.context.eventName = "discussion";
+    global.context.runId = 789;
+
+    const mockDiscussion = {
+      id: "D_kwDOABCD123",
+      number: 123,
+      title: "Test Discussion",
+      body: "Old body",
+      url: "https://github.com/testowner/testrepo/discussions/123",
+    };
+
+    // Mock the query
+    mockGithub.graphql.mockResolvedValueOnce({
+      repository: {
+        discussion: mockDiscussion,
+      },
+    });
+
+    // Mock the update and capture the body parameter
+    let capturedBody;
+    mockGithub.graphql.mockImplementationOnce((query, variables) => {
+      capturedBody = variables.body;
+      return Promise.resolve({
+        updateDiscussion: {
+          discussion: {
+            ...mockDiscussion,
+            body: variables.body,
+          },
+        },
+      });
+    });
+
+    await eval(`(async () => { ${updateDiscussionScript} })()`);
+
+    expect(mockGithub.graphql).toHaveBeenCalledTimes(2);
+    // Verify the custom footer was used
+    expect(capturedBody).toContain("Custom footer by");
+    expect(capturedBody).toContain("Custom Workflow");
+  });
 });
