diff --git a/actions/setup/js/create_discussion.cjs b/actions/setup/js/create_discussion.cjs index d72b484832..96ca571c69 100644 --- a/actions/setup/js/create_discussion.cjs +++ b/actions/setup/js/create_discussion.cjs @@ -10,7 +10,7 @@ const HANDLER_TYPE = "create_discussion"; const { getTrackerID } = require("./get_tracker_id.cjs"); const { sanitizeTitle, applyTitlePrefix } = require("./sanitize_title.cjs"); -const { replaceTemporaryIdReferences } = require("./temporary_id.cjs"); +const { generateTemporaryId, isTemporaryId, normalizeTemporaryId, getOrGenerateTemporaryId, replaceTemporaryIdReferences } = require("./temporary_id.cjs"); const { parseAllowedRepos, getDefaultTargetRepo, validateRepo, parseRepoSlug } = require("./repo_helpers.cjs"); const { removeDuplicateTitleFromDescription } = require("./remove_duplicate_title.cjs"); const { getErrorMessage } = require("./error_helpers.cjs"); @@ -460,6 +460,19 @@ async function main(config = {}) { const categoryId = resolvedCategory.id; core.info(`Using category: ${resolvedCategory.name} (${resolvedCategory.matchType})`); + // Get or generate the temporary ID for this discussion + const tempIdResult = getOrGenerateTemporaryId(message, "discussion"); + if (tempIdResult.error) { + core.warning(`Skipping discussion: ${tempIdResult.error}`); + return { + success: false, + error: tempIdResult.error, + }; + } + // At this point, temporaryId is guaranteed to be a string (not null) + const temporaryId = /** @type {string} */ tempIdResult.temporaryId; + core.info(`Processing create_discussion: title=${message.title}, bodyLength=${message.body?.length ?? 0}, temporaryId=${temporaryId}, repo=${qualifiedItemRepo}`); + // Build labels array (merge config labels with item-specific labels) const discussionLabels = [...labels, ...(Array.isArray(item.labels) ? item.labels : [])] .filter(Boolean) diff --git a/actions/setup/js/create_issue.cjs b/actions/setup/js/create_issue.cjs index 677cf7cebd..4c49a49b0f 100644 --- a/actions/setup/js/create_issue.cjs +++ b/actions/setup/js/create_issue.cjs @@ -30,7 +30,7 @@ const { sanitizeTitle, applyTitlePrefix } = require("./sanitize_title.cjs"); const { generateFooterWithMessages } = require("./messages_footer.cjs"); const { generateWorkflowIdMarker } = require("./generate_footer.cjs"); const { getTrackerID } = require("./get_tracker_id.cjs"); -const { generateTemporaryId, isTemporaryId, normalizeTemporaryId, replaceTemporaryIdReferences } = require("./temporary_id.cjs"); +const { generateTemporaryId, isTemporaryId, normalizeTemporaryId, getOrGenerateTemporaryId, replaceTemporaryIdReferences } = require("./temporary_id.cjs"); const { parseAllowedRepos, getDefaultTargetRepo, validateRepo, parseRepoSlug } = require("./repo_helpers.cjs"); const { removeDuplicateTitleFromDescription } = require("./remove_duplicate_title.cjs"); const { getErrorMessage } = require("./error_helpers.cjs"); @@ -309,31 +309,16 @@ async function main(config = {}) { } // Get or generate the temporary ID for this issue - let temporaryId = generateTemporaryId(); - if (message.temporary_id !== undefined && message.temporary_id !== null) { - if (typeof message.temporary_id !== "string") { - const error = `temporary_id must be a string (got ${typeof message.temporary_id})`; - core.warning(`Skipping issue: ${error}`); - return { - success: false, - error, - }; - } - - const rawTemporaryId = message.temporary_id.trim(); - const normalized = rawTemporaryId.startsWith("#") ? rawTemporaryId.substring(1).trim() : rawTemporaryId; - - if (!isTemporaryId(normalized)) { - const error = `Invalid temporary_id format: '${message.temporary_id}'. Temporary IDs must be in format 'aw_' followed by 3 to 8 alphanumeric characters (A-Za-z0-9). Example: 'aw_abc' or 'aw_Test123'`; - core.warning(`Skipping issue: ${error}`); - return { - success: false, - error, - }; - } - - temporaryId = normalized.toLowerCase(); + const tempIdResult = getOrGenerateTemporaryId(message, "issue"); + if (tempIdResult.error) { + core.warning(`Skipping issue: ${tempIdResult.error}`); + return { + success: false, + error: tempIdResult.error, + }; } + // At this point, temporaryId is guaranteed to be a string (not null) + const temporaryId = /** @type {string} */ tempIdResult.temporaryId; core.info(`Processing create_issue: title=${message.title}, bodyLength=${message.body?.length ?? 0}, temporaryId=${temporaryId}, repo=${qualifiedItemRepo}`); // Resolve parent: check if it's a temporary ID reference @@ -500,7 +485,9 @@ async function main(config = {}) { createdIssues.push({ ...issue, _repo: qualifiedItemRepo }); // Store the mapping of temporary_id -> {repo, number} - temporaryIdMap.set(normalizeTemporaryId(temporaryId), { repo: qualifiedItemRepo, number: issue.number }); + // temporaryId is guaranteed to be non-null because we checked tempIdResult.error above + const normalizedTempId = normalizeTemporaryId(String(temporaryId)); + temporaryIdMap.set(normalizedTempId, { repo: qualifiedItemRepo, number: issue.number }); core.info(`Stored temporary ID mapping: ${temporaryId} -> ${qualifiedItemRepo}#${issue.number}`); // Track issue for copilot assignment if needed diff --git a/actions/setup/js/create_project.cjs b/actions/setup/js/create_project.cjs index ba2d9c11fd..e3378c8cb7 100644 --- a/actions/setup/js/create_project.cjs +++ b/actions/setup/js/create_project.cjs @@ -3,7 +3,7 @@ const { loadAgentOutput } = require("./load_agent_output.cjs"); const { getErrorMessage } = require("./error_helpers.cjs"); -const { normalizeTemporaryId, isTemporaryId } = require("./temporary_id.cjs"); +const { normalizeTemporaryId, isTemporaryId, generateTemporaryId, getOrGenerateTemporaryId } = require("./temporary_id.cjs"); /** * Log detailed GraphQL error information @@ -343,6 +343,18 @@ async function main(config = {}, githubClient = null) { try { let { title, owner, owner_type, item_url } = message; + // Get or generate the temporary ID for this project + const tempIdResult = getOrGenerateTemporaryId(message, "project"); + if (tempIdResult.error) { + core.warning(`Skipping project: ${tempIdResult.error}`); + return { + success: false, + error: tempIdResult.error, + }; + } + // At this point, temporaryId is guaranteed to be a string (not null) + const temporaryId = /** @type {string} */ tempIdResult.temporaryId; + // Resolve temporary ID in item_url if present if (item_url && typeof item_url === "string") { // Check if item_url contains a temporary ID (either as URL or plain ID) diff --git a/actions/setup/js/create_project_status_update.cjs b/actions/setup/js/create_project_status_update.cjs index f888a678c8..656b6fce7e 100644 --- a/actions/setup/js/create_project_status_update.cjs +++ b/actions/setup/js/create_project_status_update.cjs @@ -375,7 +375,6 @@ async function main(config = {}, githubClient = null) { previewInfo: { projectUrl: effectiveProjectUrl, status, - title, }, }; } diff --git a/actions/setup/js/temporary_id.cjs b/actions/setup/js/temporary_id.cjs index 5c3b810d5a..35422cd713 100644 --- a/actions/setup/js/temporary_id.cjs +++ b/actions/setup/js/temporary_id.cjs @@ -96,6 +96,48 @@ function replaceTemporaryIdReferencesLegacy(text, tempIdMap) { }); } +/** + * Validate and process a temporary_id from a message + * Auto-generates a temporary ID if not provided, or validates and normalizes if provided + * + * @param {Object} message - The message object that may contain a temporary_id field + * @param {string} entityType - Type of entity (e.g., "issue", "discussion", "project") for error messages + * @returns {{temporaryId: string, error: null} | {temporaryId: null, error: string}} Result with temporaryId or error + */ +function getOrGenerateTemporaryId(message, entityType = "item") { + // Auto-generate if not provided + if (message.temporary_id === undefined || message.temporary_id === null) { + return { + temporaryId: generateTemporaryId(), + error: null, + }; + } + + // Validate type + if (typeof message.temporary_id !== "string") { + return { + temporaryId: null, + error: `temporary_id must be a string (got ${typeof message.temporary_id})`, + }; + } + + // Normalize and validate format + const rawTemporaryId = message.temporary_id.trim(); + const normalized = rawTemporaryId.startsWith("#") ? rawTemporaryId.substring(1).trim() : rawTemporaryId; + + if (!isTemporaryId(normalized)) { + return { + temporaryId: null, + error: `Invalid temporary_id format: '${message.temporary_id}'. Temporary IDs must be in format 'aw_' followed by 3 to 8 alphanumeric characters (A-Za-z0-9). Example: 'aw_abc' or 'aw_Test123'`, + }; + } + + return { + temporaryId: normalized.toLowerCase(), + error: null, + }; +} + /** * Load the temporary ID map from environment variable * Supports both old format (temporary_id -> number) and new format (temporary_id -> {repo, number}) @@ -470,6 +512,7 @@ module.exports = { generateTemporaryId, isTemporaryId, normalizeTemporaryId, + getOrGenerateTemporaryId, replaceTemporaryIdReferences, replaceTemporaryIdReferencesLegacy, loadTemporaryIdMap, diff --git a/pkg/workflow/docker_api_proxy_test.go b/pkg/workflow/docker_api_proxy_test.go index 4f8f6c9283..d6c41e65ae 100644 --- a/pkg/workflow/docker_api_proxy_test.go +++ b/pkg/workflow/docker_api_proxy_test.go @@ -15,9 +15,9 @@ func TestCollectDockerImages_APIProxyForEnginesWithLLMGateway(t *testing.T) { expectAPIProxy bool }{ { - name: "Claude engine does not include api-proxy image (supportsLLMGateway: false)", + name: "Claude engine includes api-proxy image (supportsLLMGateway: 10000)", engine: "claude", - expectAPIProxy: false, + expectAPIProxy: true, }, { name: "Copilot engine does not include api-proxy image (supportsLLMGateway: false)", @@ -25,9 +25,9 @@ func TestCollectDockerImages_APIProxyForEnginesWithLLMGateway(t *testing.T) { expectAPIProxy: false, }, { - name: "Codex engine does not include api-proxy image (supportsLLMGateway: false)", + name: "Codex engine includes api-proxy image (supportsLLMGateway: 10001)", engine: "codex", - expectAPIProxy: false, + expectAPIProxy: true, }, }