Skip to content

ci: add MemOS release publish dry run - #2172

Merged
syzsunshine219 merged 2 commits into
mainfrom
docs-sync/memos-release-publish
Jul 31, 2026
Merged

ci: add MemOS release publish dry run#2172
syzsunshine219 merged 2 commits into
mainfrom
docs-sync/memos-release-publish

Conversation

@MLittleprince

@MLittleprince MLittleprince commented Jul 27, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • add MemOS whole-repo release publish workflow with dry-run-first inspection artifacts
  • extract OpenClaw local plugin docs preview only from apps/memos-local-plugin/** while keeping public Release notes as MemOS What's Changed
  • add evidence/source_refs/readability/coverage validation and release-note methodology metadata
  • reject low-signal Plugin tab drafts: generic English/Chinese copy, raw Conventional Commit subject leakage, duplicated bullets, missing refs, overlong text, and excessive fragmentation
  • prove path filtering with fixture git repositories: non-plugin-only release noise produces no local-plugin evidence, while mixed MemOS releases keep only apps/memos-local-plugin/**
  • default dry_run=false releases to Draft GitHub Releases first; publish manually to trigger release.published
  • align inspection artifacts with the three-product runbook aliases: release-notes.md, evidence.json, docs-preview.md/json, and quality-report.json

Verification

Latest dry-run output

  • PR head: b1976f21f2e9de1db8e2fd0bb12678ab2570fc02
  • version preview: v2.0.26
  • range: v2.0.25...v2.0.26
  • source_id: openclaw-local-plugin
  • release_kind: memos_whole_repo
  • docs_product_extraction: path_filtered
  • public Release body: github_generated_whats_changed
  • docs-preview.json includes source_repo=MemTensor/MemOS, source_ref=d48d2a1ae99312a4f9f495e7abab9e4f5409aa0c, product_paths=["apps/memos-local-plugin/**"], would_create_docs_pr=false, and target files content/cn/plugin-changelog.yml / content/en/plugin-changelog.yml
  • local-plugin changed files: 1
  • important evidence coverage: 1/1
  • Plugin tab draft: V7 session defaults fix, with source_ref d48d2a1a

Safety

  • PR/pre-merge dry-run does not create tag, GitHub Release, docs PR, npm publish, OSS upload, pre, gray, or production deployment
  • verified after latest dry-run: remote refs/tags/v2.0.26 does not exist, and GitHub Release API for v2.0.26 returns 404
  • public memos-release-notes.md remains whole-repo What's Changed and does not include Plugin tab source_refs or hidden payload
  • quality-report.json records release_kind=memos_whole_repo, docs_product_extraction=path_filtered, public_release_body=github_generated_whats_changed, and all no_side_effects flags as false
  • dry_run=false requires publish_confirmation exactly equal to PUBLISH v<version>
  • dry_run=false defaults to create_draft_release=true, so release.published is only triggered after manual publish

@Memtensor-AI Memtensor-AI added area:core MOS 编排层 / 框架底座 / 跨模块问题 status:in-progress Someone or AI is working on it | 人工或 AI 正在处理 labels Jul 27, 2026
@Memtensor-AI
Memtensor-AI requested a review from WeiminLee July 27, 2026 04:23
@MLittleprince
MLittleprince marked this pull request as ready for review July 28, 2026 09:42
@Memtensor-AI

Memtensor-AI commented Jul 28, 2026

Copy link
Copy Markdown
Collaborator

🤖 Open Code Review

Target: PR #2172
Task: 2bbe8b121109ac87
Base: main
Head: docs-sync/memos-release-publish

🔍 OpenCodeReview found 18 issue(s) in this PR.


1. .github/scripts/prepare-memos-release.mjs (L774-L779)

The GitHub API version string 2026-03-10 is a future date (current date is 2026-07-31). GitHub's documented stable API version is 2022-11-28. While GitHub may accept unknown or future-dated version headers gracefully, using an unrecognized version string risks unexpected behavior or rejection. The only documented API version for the releases/generate-notes endpoint is 2022-11-28. This should be corrected to the actual stable version.

💡 Suggested Change

Before:

          headers: {
            accept: "application/vnd.github+json",
            authorization: `Bearer ${token}`,
            "content-type": "application/json",
            "x-github-api-version": "2026-03-10",
          },

After:

          headers: {
            accept: "application/vnd.github+json",
            authorization: `Bearer ${token}`,
            "content-type": "application/json",
            "x-github-api-version": "2022-11-28",
          },

2. .github/scripts/prepare-memos-release.mjs (L1318-L1322)

The heredoc delimiter EOF is hardcoded without any check that the output value itself contains a line consisting solely of EOF. If any multi-line value (e.g., existingTag.message, evidence.skip_reason, or release note bodies passed through appendOutput) contains \nEOF\n, the GITHUB_OUTPUT file will be truncated at that point and downstream steps will read incorrect or missing values. This is a known correctness issue with naive heredoc usage in GitHub Actions output. Use a randomized or collision-resistant delimiter, or sanitize newlines for single-line values.

💡 Suggested Change

Before:

function appendOutput(name, value) {
  if (!process.env.GITHUB_OUTPUT) return;
  const text = String(value ?? "");
  writeFileSync(process.env.GITHUB_OUTPUT, `${name}<<EOF\n${text}\nEOF\n`, { flag: "a" });
}

After:

function appendOutput(name, value) {
  if (!process.env.GITHUB_OUTPUT) return;
  const text = String(value ?? "");
  // Use a random delimiter to prevent collision with output content
  const delimiter = `EOF_${Math.random().toString(36).slice(2)}`;
  writeFileSync(process.env.GITHUB_OUTPUT, `${name}<<${delimiter}\n${text}\n${delimiter}\n`, { flag: "a" });
}

3. .github/scripts/prepare-memos-release.mjs (L582-L584)

For every commit in the git log range, commitBodyExcerpt spawns a separate synchronous execFileSync("git show") subprocess. For releases with hundreds of commits, this creates an O(N) subprocess overhead that blocks the Node.js event loop and can cause CI timeouts. Instead, retrieve all commit bodies in a single git log call using a separator-delimited format (e.g., --format=%H%x00%B%x00 with a NUL separator), or at minimum apply a commit count limit before entering this loop.

💡 Suggested Change

Before:

  const commits = parseLines(commitText).map((line) => {
    const [sha = "", shortSha = "", author = "", date = "", subject = ""] = line.split("\t");
    const bodyExcerpt = commitBodyExcerpt(sha);

After:

  // Retrieve all commit bodies in a single git log call to avoid O(N) subprocess invocations
  const bodyMap = new Map();
  const rawBodies = tryGit([
    "log",
    "--format=%H%x00%B%x1e",
    range,
    "--",
    PRODUCT_PATH,
  ]);
  for (const entry of rawBodies.split("\x1e").filter(Boolean)) {
    const nulIndex = entry.indexOf("\x00");
    if (nulIndex === -1) continue;
    const sha = entry.slice(0, nulIndex).trim();
    const body = redact(entry.slice(nulIndex + 1)).slice(0, 24000);
    bodyMap.set(sha, body);
  }

  const commits = parseLines(commitText).map((line) => {
    const [sha = "", shortSha = "", author = "", date = "", subject = ""] = line.split("\t");
    const bodyExcerpt = bodyMap.get(sha) ?? "";

4. .github/scripts/prepare-memos-release.mjs (L1149-L1151)

When ALLOW_OFFLINE_DOCS_PREVIEW=true and no Doc Agent credentials are available, requestDocAgentDraft returns the offline fallback draft directly via normalizeDraft(localFallbackDraft(evidence)). Back in run(), this draft is then passed to validateDraft(draft, evidence), which will fail if required_source_refs are not covered by the fallback's release_items. The offline fallback uses heuristic pattern-matching (FALLBACK_TOPIC_RULES) and may not cover all required_source_refs, causing run() to throw even though ALLOW_OFFLINE_DOCS_PREVIEW was explicitly set to permit degraded output. The test file confirms this path exists but does not test the run() integration. Consider either skipping the outer validateDraft when the draft originates from the offline fallback, or marking the fallback draft with needs_review: true and downgrading the outer validation to a warning.


5. .github/scripts/prepare-memos-release.mjs (L804-L810)

The FALLBACK_TOPIC_RULES array contains hardcoded, historically-specific release note text. Any future commit whose subject happens to match a pattern (e.g., any commit mentioning logging or bridge) will unconditionally produce the exact same predefined Chinese and English bullet, regardless of what the actual change was. This creates a silent risk of publishing factually incorrect release notes in offline fallback mode. Additionally, the offline fallback sets needs_review: false and confidence: "medium", so a human reviewer has no signal that the content is pattern-matched rather than evidence-derived. At minimum, the fallback draft should set needs_review: true to flag it for human review before publishing.

💡 Suggested Change

Before:

  const FALLBACK_TOPIC_RULES = [
  {
    pattern: /openrouter/i,
    category: "Added",
    text_cn: "**OpenRouter 提供商路由**:新增 OpenRouter 路由与 reasoning 配置支持,便于按配置选择模型提供商。",
    text_en: "**OpenRouter provider routing**: Added OpenRouter routing and reasoning configuration support for model selection.",
  },

After:

  return {
    ok: true,
    needs_review: true,  // Always require human review for offline fallback drafts
    confidence: items.length ? "medium" : "high",
    warnings: ["offline fallback draft; use GitHub Actions with Doc Agent secrets for production quality"],
    release_items: items,

6. .github/scripts/prepare-memos-release.test.mjs (L962-L974)

The two async tests that mock globalThis.fetch and mutate multiple process.env variables run under node --test without --test-concurrency=1. Node.js 18+ runs top-level tests concurrently by default, so the global state mutations in these tests can race with each other and with the synchronous withFixtureRepo tests that call process.chdir().

Specifically:

  • One test's globalThis.fetch mock can intercept fetch() calls intended for the other test, making one test's fake always return invalidDraft while the other expects validDraft on the 4th call.
  • process.env.ALLOW_OFFLINE_DOCS_PREVIEW deletion/restoration is not atomic, so the other test may read undefined mid-execution when it expects "true".
  • process.chdir() in withFixtureRepo (which runs in many synchronous tests) changes the working directory for all concurrently executing tests, including the async ones that call generateGitHubReleaseNotes (which uses tryGit(["log", ...]) internally via process.cwd()).

Suggested fix: Either pass --test-concurrency=1 in the workflow node --test invocation, or use test's built-in mock API to scope the mocks properly:

# In the workflow files:
run: node --test --test-concurrency=1 .github/scripts/prepare-memos-release.test.mjs

Or wrap async tests with Node's test.mock / restore hooks to avoid global mutation.


7. .github/scripts/prepare-memos-release.test.mjs (L125-L146)

Temporary directories created by mkdtempSync in withFixtureRepo are never cleaned up. The helper is used in at least 9 test cases, each writing a full git repo with history into os.tmpdir(). Neither a finally block with rmSync(root, { recursive: true }) nor any other cleanup mechanism is present.

While ephemeral CI environments discard the runner disk, running the tests locally will accumulate unbounded memos-release-evidence-* directories. More importantly, the orphaned .git directories can interfere with git commands run from the original working directory if git walks parent directories (e.g., auto-discovery of a repo root).

Suggested fix: Clean up the temp dir in the finally block:

function withFixtureRepo(fn) {
  const originalCwd = process.cwd();
  const root = mkdtempSync(join(tmpdir(), "memos-release-evidence-"));
  try {
    process.chdir(root);
    // ... setup ...
    return fn(root);
  } finally {
    process.chdir(originalCwd);
    // Add cleanup:
    import { rmSync } from "node:fs";
    rmSync(root, { recursive: true, force: true });
  }
}
💡 Suggested Change

Before:

function withFixtureRepo(fn) {
  const originalCwd = process.cwd();
  const root = mkdtempSync(join(tmpdir(), "memos-release-evidence-"));
  try {
    process.chdir(root);
    git(["init", "-q"]);
    git(["config", "user.email", "release-test@example.invalid"]);
    git(["config", "user.name", "Release Test"]);
    writeRepoFile(
      "apps/memos-local-plugin/package.json",
      `${JSON.stringify({ name: "@memtensor/memos-local-plugin", version: "9.9.0" }, null, 2)}\n`,
    );
    writeRepoFile("apps/memos-local-plugin/src/index.js", "export const baseline = true;\n");
    writeRepoFile("memos/core/session.js", "export const sessionCore = true;\n");
    writeRepoFile("packages/memos-sdk/index.js", "export const sdk = true;\n");
    commitAll("chore: baseline release");
    git(["tag", "v9.9.0"]);
    return fn(root);
  } finally {
    process.chdir(originalCwd);
  }
}

After:

function withFixtureRepo(fn) {
  const originalCwd = process.cwd();
  const root = mkdtempSync(join(tmpdir(), "memos-release-evidence-"));
  try {
    process.chdir(root);
    git(["init", "-q"]);
    git(["config", "user.email", "release-test@example.invalid"]);
    git(["config", "user.name", "Release Test"]);
    writeRepoFile(
      "apps/memos-local-plugin/package.json",
      `${JSON.stringify({ name: "@memtensor/memos-local-plugin", version: "9.9.0" }, null, 2)}\n`,
    );
    writeRepoFile("apps/memos-local-plugin/src/index.js", "export const baseline = true;\n");
    writeRepoFile("memos/core/session.js", "export const sessionCore = true;\n");
    writeRepoFile("packages/memos-sdk/index.js", "export const sdk = true;\n");
    commitAll("chore: baseline release");
    git(["tag", "v9.9.0"]);
    return fn(root);
  } finally {
    process.chdir(originalCwd);
    rmSync(root, { recursive: true, force: true });
  }
}

8. .github/scripts/prepare-memos-release.test.mjs (L713)

The git revert --no-commit call does not handle potential conflicts. If git cannot cleanly apply the revert (even in this minimal fixture), the command will leave the repository in a conflicted state and the subsequent git commit will fail with an unhelpful error.

Moreover, execFileSync is called without a timeout option (the default is no timeout). In pathological cases — for example if git waits for a conflict resolution prompt on certain git versions — the test process would hang indefinitely, blocking the CI runner.

Suggested fix: Use git revert --no-edit to commit the revert in a single step and avoid staging issues, or add a timeout and error handling:

// Option A: Single-step revert (simpler and avoids the staged-state edge case)
git(["revert", "--no-edit", featureSha]);

// Option B: If two-step is required, add a timeout guard
git(["revert", "--no-commit", "--no-edit", featureSha]);
💡 Suggested Change

Before:

    git(["revert", "--no-commit", featureSha]);

After:

    git(["revert", "--no-edit", featureSha]);

9. .github/scripts/prepare-memos-release.test.mjs (L751-L761)

The "GitHub release notes fallback stays whole-repo when API access is unavailable" async test creates its own temp directory and git repo using the same process.chdir() / restore pattern as withFixtureRepo, but it is declared as an async test. Since node --test runs top-level tests concurrently, this test's process.chdir(root) executes at an indeterminate time relative to all synchronous withFixtureRepo tests.

A synchronous test calling collectLocalPluginEvidence (which calls sh(["log", ...]) using process.cwd()) could observe the async test's temporary repo path as its working directory, causing git operations to run in the wrong repository and return incorrect or empty results.

Suggested fix: Refactor this test to avoid process.chdir() altogether by passing the cwd explicitly to git operations, or run it within withFixtureRepo (extended to support async callbacks), or enforce serial execution via --test-concurrency=1.


10. .github/workflows/memos-local-plugin-publish.yml (L31-L35)

The guard-legacy-publish job is added to both workflow_dispatch and workflow_call triggers. However, the confirmation string "LEGACY PUBLISH memos-local-plugin-v<version>" is trivially constructable by any caller workflow that invokes this via workflow_call. Since workflow_call callers run in the same trusted context as the callee, a caller can always supply legacy_publish_confirmation: "LEGACY PUBLISH memos-local-plugin-v${{ inputs.version }}" with dry_run: false and bypass the guard entirely without any human interaction.

The guard is only meaningful as a friction mechanism for human workflow_dispatch users, but as written it also applies (and can be trivially satisfied) for machine-to-machine workflow_call invocations. The other added workflows (memos-release-publish.yml) will presumably call this via workflow_call and must always supply this string — creating maintenance friction with no real security benefit for that path.

Suggestion: Restrict the guard to workflow_dispatch only using github.event_name, so programmatic workflow_call invocations are not required to provide the confirmation string:

if [ "${DRY_RUN}" = "true" ]; then
  echo "dry_run=true; legacy standalone publish confirmation is not required."
  exit 0
fi

if [ "${{ github.event_name }}" != "workflow_dispatch" ]; then
  echo "workflow_call path; skipping legacy confirmation check."
  exit 0
fi

expected="LEGACY PUBLISH memos-local-plugin-v${RELEASE_VERSION}"
...

Alternatively, if the intent is that all callers (including workflow_call) must confirm, document this expectation explicitly and ensure that the calling workflows supply it intentionally.

💡 Suggested Change

Before:

      legacy_publish_confirmation:
        description: "Required only when dry_run=false. Must exactly equal: LEGACY PUBLISH memos-local-plugin-v<version>"
        required: false
        type: string
        default: ""

After:

      legacy_publish_confirmation:
        description: "Required only when dry_run=false AND event is workflow_dispatch. Must exactly equal: LEGACY PUBLISH memos-local-plugin-v<version>"
        required: false
        type: string
        default: ""

11. .github/workflows/memos-local-plugin-publish.yml (L93-L96)

The GITHUB_EVENT_NAME (i.e., ${{ github.event_name }}) is used inside the script via a direct expression substitution rather than an env var. To check the event name inside the guard script (see suggestion on the first comment), it should be passed through env: to avoid any potential injection:

env:
  DRY_RUN: ${{ inputs.dry_run }}
  RELEASE_VERSION: ${{ inputs.version }}
  LEGACY_PUBLISH_CONFIRMATION: ${{ inputs.legacy_publish_confirmation }}
  EVENT_NAME: ${{ github.event_name }}

Then use ${EVENT_NAME} in the shell script instead of embedding ${{ github.event_name }} directly in run:.

💡 Suggested Change

Before:

        env:
          DRY_RUN: ${{ inputs.dry_run }}
          RELEASE_VERSION: ${{ inputs.version }}
          LEGACY_PUBLISH_CONFIRMATION: ${{ inputs.legacy_publish_confirmation }}

After:

        env:
          DRY_RUN: ${{ inputs.dry_run }}
          RELEASE_VERSION: ${{ inputs.version }}
          LEGACY_PUBLISH_CONFIRMATION: ${{ inputs.legacy_publish_confirmation }}
          EVENT_NAME: ${{ github.event_name }}

12. .github/workflows/memos-release-post-merge-dry-run.yml (L112-L117)

The Summarize inspection step is missing TARGET_REF and TARGET_SHA from both the env: block and the summary output, even though prepare-memos-release.mjs writes both to GITHUB_OUTPUT (via appendOutput('target_ref', ...) and appendOutput('target_sha', ...)). The equivalent publish workflow (memos-release-publish.yml) includes these in its summary. Without them, it's impossible to tell from the step summary which exact commit was inspected in this post-merge smoke check, making it harder to diagnose discrepancies between runs.

Suggested fix: add TARGET_REF and TARGET_SHA to both the env: block and the echo lines in the summary.

💡 Suggested Change

Before:

          DOCS_ACTION: ${{ steps.prepare.outputs.docs_action }}
          SKIP_REASON: ${{ steps.prepare.outputs.skip_reason }}
          RELEASE_NOTES_SOURCE: ${{ steps.prepare.outputs.release_notes_source }}
        run: |
          {
            echo "## MemOS release post-merge dry run"

After:

          DOCS_ACTION: ${{ steps.prepare.outputs.docs_action }}
          SKIP_REASON: ${{ steps.prepare.outputs.skip_reason }}
          RELEASE_NOTES_SOURCE: ${{ steps.prepare.outputs.release_notes_source }}
          TARGET_REF: ${{ steps.prepare.outputs.target_ref }}
          TARGET_SHA: ${{ steps.prepare.outputs.target_sha }}
        run: |
          {
            echo "## MemOS release post-merge dry run"

13. .github/workflows/memos-release-post-merge-dry-run.yml (L138-L139)

The Summarize inspection step does not echo target_ref or target_sha into the step summary, making it difficult to identify the exact commit that was inspected. The publish workflow includes these fields. Add the missing echo lines after the release_notes_source line.

💡 Suggested Change

Before:

            echo "- release_notes_source: ${RELEASE_NOTES_SOURCE}"
            echo "- offline_docs_preview: true"

After:

            echo "- release_notes_source: ${RELEASE_NOTES_SOURCE}"
            echo "- target_ref: ${TARGET_REF}"
            echo "- target_sha: ${TARGET_SHA}"
            echo "- offline_docs_preview: true"

14. .github/workflows/memos-release-pre-merge-dry-run.yml (L113-L117)

The 'Summarize inspection' run: block is missing set -euo pipefail at the top, unlike every other run: block in this workflow (and the identical step in the sibling memos-release-post-merge-dry-run.yml). Without set -u, variables that are unset or empty (e.g. PUBLISH_BLOCKED, HAS_PRODUCT_CHANGES, DOCS_ACTION, RELEASE_NOTES_SOURCE, LOCAL_PLUGIN_VERSION, etc.) will silently expand to empty strings rather than causing the step to fail. This hides potential issues where a prior step failed to produce an expected output, resulting in a misleading summary being written to $GITHUB_STEP_SUMMARY with blank values.

Add set -euo pipefail as the first line of this run: block for consistency and reliability. Variables that are intentionally optional already use ${VAR:-n/a} defaults, so enabling set -u would only surface genuinely missing required values.

💡 Suggested Change

Before:

        run: |
          {
            echo "## MemOS release pre-merge dry run"
            echo ""
            echo "- inferred_version: ${VERSION}"

After:

        run: |
          set -euo pipefail
          {
            echo "## MemOS release pre-merge dry run"
            echo ""
            echo "- inferred_version: ${VERSION}"

15. .github/workflows/memos-release-pre-merge-dry-run.yml (L14)

The || github.ref fallback in the concurrency group is dead code. This workflow is triggered exclusively by pull_request events, so github.event.pull_request.number is always defined and non-empty. The || github.ref branch will never be evaluated.

The sibling memos-release-post-merge-dry-run.yml (push-triggered) uses a static group name memos-release-post-merge-dry-run-main instead, which is the cleaner pattern. Consider removing the dead fallback to avoid confusion for future maintainers who may assume this workflow can also be triggered by push events.

💡 Suggested Change

Before:

  group: memos-release-pre-merge-dry-run-${{ github.event.pull_request.number || github.ref }}

After:

  group: memos-release-pre-merge-dry-run-${{ github.event.pull_request.number }}

16. .github/workflows/memos-release-publish.yml (L62-L63)

The --force flag on git fetch --tags silently overwrites any locally cached tag refs with whatever is on the remote. While this is intentional for fetching the latest remote state, it could mask a situation where a local tag already exists at a different SHA after the shallow checkout. Using --force here is generally safe in a fresh CI runner, but it's worth being explicit: if the checkout step ever pre-fetches tags (e.g., via fetch-tags: true on actions/checkout), the --force flag would silently discard those refs and replace them with remote values without any warning. Consider adding a comment explaining why --force is intentional, or remove --force if the checkout never pre-fetches tags.


17. .github/workflows/memos-release-publish.yml (L224-L228)

The draft release existence check has subtly incorrect logic for the CREATE_DRAFT_RELEASE=true case. When a draft GitHub Release already exists and CREATE_DRAFT_RELEASE=true, the condition [ "${is_draft}" = "true" ] && [ "${CREATE_DRAFT_RELEASE}" != "true" ] evaluates to false, so the workflow falls through to the ::notice:: line and exits without error — even if the existing draft has stale release notes, points to a different target SHA, or was created by a previous failed run. This means rerunning the workflow after a partial failure will silently leave an incorrect draft in place rather than updating or failing explicitly. Consider adding a check that validates the existing draft release's target SHA matches TARGET_SHA before accepting it as-is.

💡 Suggested Change

Before:

            if [ "${is_draft}" = "true" ] && [ "${CREATE_DRAFT_RELEASE}" != "true" ]; then
              echo "::error::GitHub Release ${CURRENT_TAG} already exists as draft (${release_url}); publish or delete it manually before rerunning."
              exit 1
            fi
            echo "::notice::GitHub Release ${CURRENT_TAG} already exists (${release_url}); leaving it unchanged."

After:

            if [ "${is_draft}" = "true" ] && [ "${CREATE_DRAFT_RELEASE}" != "true" ]; then
              echo "::error::GitHub Release ${CURRENT_TAG} already exists as draft (${release_url}); publish or delete it manually before rerunning."
              exit 1
            fi
            # Warn that the existing release (draft or published) is left unchanged.
            # Operators should verify the release notes and target SHA are correct.
            echo "::warning::GitHub Release ${CURRENT_TAG} already exists (${release_url}); leaving it unchanged. Verify release notes and target SHA are correct before publishing."

18. .github/workflows/memos-release-publish.yml (L40-L41)

The workflow-level permissions: contents: write grants write access to all jobs in this workflow. This is appropriate for the release job, but applying it at the workflow level rather than the job level means any future jobs added to this workflow will inherit this elevated permission by default — violating the principle of least privilege. Move the permission to the release job level so it's scoped only where needed.

💡 Suggested Change

Before:

permissions:
  contents: write

After:

permissions: {}

jobs:
  release:
    permissions:
      contents: write

Generated by cloud-assistant via Open Code Review.

@Memtensor-AI

Copy link
Copy Markdown
Collaborator

✅ Automated Test Results: PASSED

All tests passed (1/1 executed). memos_github_open_source/smoke: 1/1. Duration: 1s

Branch: docs-sync/memos-release-publish

@Memtensor-AI Memtensor-AI added status:ready Ready for implementation; waiting for assignee or AI dispatch | 可进入实现,等待认领或派发 status:in-progress Someone or AI is working on it | 人工或 AI 正在处理 and removed status:in-progress Someone or AI is working on it | 人工或 AI 正在处理 status:ready Ready for implementation; waiting for assignee or AI dispatch | 可进入实现,等待认领或派发 labels Jul 28, 2026
@MLittleprince
MLittleprince force-pushed the docs-sync/memos-release-publish branch from 3b328f9 to 9ec8674 Compare July 31, 2026 06:43
@Memtensor-AI

Copy link
Copy Markdown
Collaborator

✅ Automated Test Results: PASSED

All tests passed (1/1 executed). memos_github_open_source/smoke: 1/1. Duration: 1s

Branch: docs-sync/memos-release-publish

@Memtensor-AI Memtensor-AI added status:ready Ready for implementation; waiting for assignee or AI dispatch | 可进入实现,等待认领或派发 and removed status:in-progress Someone or AI is working on it | 人工或 AI 正在处理 labels Jul 31, 2026
@syzsunshine219
syzsunshine219 merged commit 027dc89 into main Jul 31, 2026
19 checks passed
@syzsunshine219
syzsunshine219 deleted the docs-sync/memos-release-publish branch July 31, 2026 09:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:core MOS 编排层 / 框架底座 / 跨模块问题 status:ready Ready for implementation; waiting for assignee or AI dispatch | 可进入实现,等待认领或派发

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants