Skip to content

feat(appscript): add push, pull, deploy, and version management - #1005

Open
haosdent wants to merge 1 commit into
openclaw:mainfrom
haosdent:feat/appscript-push-deploy
Open

feat(appscript): add push, pull, deploy, and version management#1005
haosdent wants to merge 1 commit into
openclaw:mainfrom
haosdent:feat/appscript-push-deploy

Conversation

@haosdent

Copy link
Copy Markdown

Scope

gog appscript could read a project (get, content) and run a deployed
function, but never change one. This adds the write half so an Apps Script
project can be developed and released from the CLI:

Command Purpose
appscript push <scriptId> <dir> Replace project content from a local directory
appscript pull <scriptId> <dir> Write project content into a local directory
appscript deploy <scriptId> Cut a version and deploy it (prints the web app URL)
appscript deployments <scriptId> List deployments
appscript undeploy <scriptId> <deploymentId> Delete a deployment
appscript versions <scriptId> List versions

Notes on the design

  • deploy --deployment-id updates in place, so a redeploy keeps the same
    web app URL. Without it, every deploy mints a new URL, which is the main
    thing that makes scripted redeploys painful today.
  • Extension mapping. Apps Script stores a file's extension in its type,
    not its name, so push/pull translate between Code.gs and
    {name: "Code", type: SERVER_JS}. Only appsscript.json is accepted as
    JSON, so a stray package-lock.json in the directory is skipped without
    being read.
  • The manifest is mandatory. UpdateContent rejects a payload without
    appsscript.json; push checks up front and says so, instead of passing
    through an opaque API error.
  • Pagination. Both list commands use loadPagedItems /
    writePagedJSONResult / printNextPageHintWithAll, so --max, --page,
    --all, and --fail-empty behave as they do elsewhere. This matters here:
    the API defaults to 50 per page and deploy cuts a version every time, so
    dropping the next page token would silently under-report versions after a
    few months of use.
  • undeploy goes through dryRunAndConfirmDestructive — deleting a
    deployment permanently breaks its web app URL, so it honours -y/--force
    and fails closed under --no-input.
  • Safety profiles. readonly.yaml and agent-safe.yaml are allow-lists
    that fail closed, so the six new leaves are listed explicitly. Without this,
    even the read-only pull, deployments, and versions would be rejected
    (and hidden from help) in a baked build.
  • --readonly needs no per-command code: script.googleapis.com is not on
    the POST allowlist in internal/googleapi/read_only.go, so push, deploy,
    and undeploy are already refused at the transport. pull still writes
    local files under --readonly, matching drive download.

New flags

  • appscript deploy: --description, --deployment-id
  • appscript deployments, appscript versions: the standard
    --max / --page / --all / --fail-empty set

Testing

  • make ci passes (fmt-check, lint, deadcode, test, docs-check,
    agent-skills-check).
  • New tests in appscript_sync_test.go and appscript_deploy_test.go cover
    the extension mapping, manifest enforcement, non-manifest JSON skipping,
    push/pull round trips, pulled-file permissions and path-traversal
    containment, --all across two pages, nextPageToken retention in
    --json, and the undeploy confirmation gate.
  • dryrun_e2e_test.go gains rows for appscript push/pull/deploy/undeploy;
    TestReadonlySafetyProfileBlocksNestedMutations gains the two new
    mutations.
  • Generated docs and agent skills were regenerated (make docs-commands,
    make agent-skills).

Two open questions for maintainers

  1. Should push also require confirmation? UpdateContent replaces the
    project wholesale, so any remote file absent from the local directory is
    deleted — drive sync push deliberately advertises "no remote deletes",
    so this repo is more conservative here. I left push without a prompt so it
    stays usable non-interactively, and instead spelled the behaviour out in
    the help text. Happy to route it through dryRunAndConfirmDestructive if
    you'd prefer.
  2. Command shape. deploy / deployments / undeploy are flat sibling
    verbs over one sub-resource, whereas the rest of the CLI nests
    (drive comments {list,create,delete}). A nested
    appscript deployments {list,create,delete} would also make the safety
    profile and --enable-commands entries one block instead of three
    scattered keys. I kept the flat shape to preserve appscript deploy as
    the ergonomic one-liner, but I'll restructure if you want consistency.

🤖 Generated with Claude Code

`gog appscript` could read a project but never change one: there was no way
to push local sources, cut a version, or manage web app deployments.

Add six commands:

  appscript push <scriptId> <dir>         replace project content from a directory
  appscript pull <scriptId> <dir>         write project content into a directory
  appscript deploy <scriptId>             cut a version and deploy it
  appscript deployments <scriptId>        list deployments
  appscript undeploy <scriptId> <id>      delete a deployment
  appscript versions <scriptId>           list versions

`deploy --deployment-id` updates an existing deployment in place, so a
redeploy keeps the same web app URL.

Apps Script stores a file's extension in its type rather than its name, so
push and pull translate between `Code.gs` and `{name: "Code", type:
SERVER_JS}`. A manifest is mandatory -- UpdateContent rejects a payload
without appsscript.json -- so push fails early with a clear message rather
than surfacing an opaque API error.

Both list commands go through the shared pagination helpers, so `--max`,
`--page`, `--all`, and `--fail-empty` behave as they do elsewhere; the API
defaults to 50 items per page and `deploy` cuts a version every time, so
dropping the next page token would silently under-report versions. Deleting
a deployment permanently breaks its web app URL, so `undeploy` goes through
`dryRunAndConfirmDestructive`.

The new leaves are also added to the bundled safety profiles: those are
allow-lists that fail closed, so without an entry even the read-only `pull`,
`deployments`, and `versions` would be rejected in a baked build.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@clawsweeper

clawsweeper Bot commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

🦞👀
ClawSweeper picked this up.

Pull request received. I will update this pull request when review starts.

@clawsweeper clawsweeper Bot added merge-risk: 🚨 security-boundary 🚨 Merging this PR could weaken sandboxing, authorization, credentials, or sensitive data. P1 Urgent regression or broken agent/channel workflow affecting real users now. rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. labels Aug 20, 2026
@clawsweeper

clawsweeper Bot commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

Codex review: needs real behavior proof before merge. Reviewed August 19, 2026, 9:29 PM ET / August 20, 2026, 01:29 UTC.

ClawSweeper review

What this changes

The PR adds CLI commands to push and pull Apps Script project files, create and manage deployments, and list project versions.

Merge readiness

Blocked until real behavior proof is added - 11 items remain

Keep open: current main lacks these Apps Script commands, but the proposed push path can follow local symlinks and upload data outside the selected project directory. The new permanent command surface and destructive-push policy also need maintainer direction, and live Google API proof is missing.

Priority: P1
Reviewed head: 331b9da14b771194102ebc93d3211865b4370332
Owner decision: Required. See Decision needed.

Review scores

Measure Result What it means
Overall readiness 🧂 unranked krab (1/6) The PR has substantial unit coverage, but a security-relevant source flaw and missing real Google API proof prevent a quality-ready verdict.
Proof confidence 🧂 unranked krab (1/6) Needs real behavior proof before merge: The PR reports CI but provides no redacted after-fix run against a real Apps Script project; add a terminal transcript or recording that shows the changed behavior and redact private IDs, URLs, and tokens. After adding proof, update the PR body; ClawSweeper should re-review automatically. If it does not, the PR author or someone with repository write access can comment @clawsweeper re-review.
Patch quality 🦪 silver shellfish (2/6) Security review found an item that needs attention.

Verification

Check Result Evidence
Real behavior Needs proof Needs real behavior proof before merge: The PR reports CI but provides no redacted after-fix run against a real Apps Script project; add a terminal transcript or recording that shows the changed behavior and redact private IDs, URLs, and tokens. After adding proof, update the PR body; ClawSweeper should re-review automatically. If it does not, the PR author or someone with repository write access can comment @clawsweeper re-review.
Evidence reviewed 5 items Current main boundary: Current main exposes only get, content, run, and create under the Apps Script command group; the requested sync and deployment commands are not already implemented.
Symlink upload path: The new source reader skips directories but reads every allowed-extension non-directory entry; a symlink named Code.gs is therefore followed by os.ReadFile and its target is included in UpdateContent.
Dependency contract applies: The patch directly imports and calls the Google Apps Script v1 client for project content and deployment operations, so its API behavior is part of this review boundary.
Findings 1 actionable finding [P1] Reject symlinked sources before uploading project files
Security Needs attention Symlinked source files escape the selected directory: A symlink with an accepted extension is treated as a regular source file and read before upload, bypassing the intended local project-directory boundary.

How this fits together

The Apps Script command group converts local project files and CLI flags into Google Apps Script API requests, then reports project, deployment, or version data. This PR adds filesystem synchronization and deployment lifecycle operations to that existing API client.

flowchart LR
  A[Local project directory] --> B[Apps Script CLI commands]
  C[CLI flags and safety profiles] --> B
  B --> D[Input validation]
  D --> E[Google Apps Script API]
  E --> F[Project content and deployments]
  E --> G[CLI output]
  E --> H[Pulled local files]
Loading

Decision needed

Question Recommendation
Should gog accept this expanded Apps Script command surface with a whole-project-replacing push, and if so should push require explicit confirmation and the deployment operations remain flat verbs? Sponsor a guarded surface: Require the symlink fix, live proof, an explicit push confirmation policy, and a settled command hierarchy before merge.

Why: This is a large new API surface with an intentional destructive behavior; repository vision requires maintainer direction rather than inferring a permanent CLI contract from the implementation alone.

Before merge

  • Add real behavior proof - Needs real behavior proof before merge: The PR reports CI but provides no redacted after-fix run against a real Apps Script project; add a terminal transcript or recording that shows the changed behavior and redact private IDs, URLs, and tokens. After adding proof, update the PR body; ClawSweeper should re-review automatically. If it does not, the PR author or someone with repository write access can comment @clawsweeper re-review.
  • Reject symlinked sources before uploading project files (P1) - IsDir does not reject a symlink, so a Code.gs link is followed by os.ReadFile and its target is sent to Apps Script. That lets a project directory upload a file outside its boundary; reject symlink entries before reading and add a regression test.
  • Resolve security concern: Symlinked source files escape the selected directory - A symlink with an accepted extension is treated as a regular source file and read before upload, bypassing the intended local project-directory boundary.
  • Resolve merge risk (P1) - appscript push follows symlinked source files and can upload data outside the chosen project directory.
  • Resolve merge risk (P1) - The PR creates a large, permanent Apps Script CLI surface while its flat command shape and whole-project replacement policy remain explicitly undecided.
  • Resolve merge risk (P1) - The PR provides no redacted after-fix run against a real Apps Script project, so Google API behavior and deployment output remain unverified.
  • Complete next step (P2) - Maintainers must decide the new command contract; the contributor must also provide live proof, while the symlink fix is a concrete prerequisite.
  • Improve patch quality - Reject symlinked inputs and add focused regression coverage.
  • Improve patch quality - Add redacted live proof of a real push, pull, or deployment flow after the fix.
  • Improve patch quality - Obtain maintainer direction on confirmation and command hierarchy.

Findings

  • [P1] Reject symlinked sources before uploading project files — internal/cmd/appscript_sync.go:200-223
  • [medium] Symlinked source files escape the selected directory — internal/cmd/appscript_sync.go:223
Agent review details

Security

Needs attention: The new upload path follows symlinks and can disclose an external local file to the configured Apps Script project.

Review metrics

Metric Value Why it matters
Changed surface 21 files, +1492 / -8 lines This is a large addition of a new command surface, safety-profile entries, generated documentation, and tests.
Production versus tests production +647, tests +528 The implementation is substantially covered by unit tests, but those tests do not replace required live Google API proof.

Merge-risk options

Maintainer options:

  1. Contain push inputs before merge (recommended)
    Reject symlinked source entries before reading them and add a regression test that proves a link cannot upload an external file.
  2. Pause for a safety decision
    Defer the PR if maintainers do not want a project-replacing push operation in the core CLI.

Technical review

Best possible solution:

Reject symlinked push inputs, add regression coverage and redacted live Apps Script proof, then land only after maintainers choose the destructive-push and command-hierarchy policy.

Do we have a high-confidence way to reproduce the issue?

Yes, source-reproducible: place a symlink named Code.gs in the selected directory pointing outside it; the added reader treats it as a file and passes its contents to the upload request. This was established by source review, not executed against a live account.

Is this the best way to solve the issue?

No: the feature fits an existing area, but it needs a symlink boundary fix, live provider proof, and maintainer confirmation of the permanent destructive-push and command-shape contract.

Full review comments:

  • [P1] Reject symlinked sources before uploading project files — internal/cmd/appscript_sync.go:200-223
    IsDir does not reject a symlink, so a Code.gs link is followed by os.ReadFile and its target is sent to Apps Script. That lets a project directory upload a file outside its boundary; reject symlink entries before reading and add a regression test.
    Confidence: 0.95

Overall correctness: patch is incorrect
Overall confidence: 0.95

AGENTS.md: found and applied where relevant.

Codex review notes: model internal, reasoning high; reviewed against eb85a99366eb.

Labels

Label changes:

  • add P1: The proposed push command can upload an external local file through a symlinked project entry.
  • add merge-risk: 🚨 security-boundary: Merging the branch would introduce a local-file boundary bypass in a command that uploads content to Apps Script.
  • add rating: 🧂 unranked krab: Overall readiness is 🧂 unranked krab; proof is 🧂 unranked krab and patch quality is 🦪 silver shellfish.
  • add status: 📣 needs proof: The PR needs real behavior proof before ClawSweeper can clear the contributor ask. Needs real behavior proof before merge: The PR reports CI but provides no redacted after-fix run against a real Apps Script project; add a terminal transcript or recording that shows the changed behavior and redact private IDs, URLs, and tokens. After adding proof, update the PR body; ClawSweeper should re-review automatically. If it does not, the PR author or someone with repository write access can comment @clawsweeper re-review.

Label justifications:

  • P1: The proposed push command can upload an external local file through a symlinked project entry.
  • merge-risk: 🚨 security-boundary: Merging the branch would introduce a local-file boundary bypass in a command that uploads content to Apps Script.
  • rating: 🧂 unranked krab: Overall readiness is 🧂 unranked krab; proof is 🧂 unranked krab and patch quality is 🦪 silver shellfish.
  • status: 📣 needs proof: The PR needs real behavior proof before ClawSweeper can clear the contributor ask. Needs real behavior proof before merge: The PR reports CI but provides no redacted after-fix run against a real Apps Script project; add a terminal transcript or recording that shows the changed behavior and redact private IDs, URLs, and tokens. After adding proof, update the PR body; ClawSweeper should re-review automatically. If it does not, the PR author or someone with repository write access can comment @clawsweeper re-review.

Evidence

Security concerns:

  • [medium] Symlinked source files escape the selected directory — internal/cmd/appscript_sync.go:223
    A symlink with an accepted extension is treated as a regular source file and read before upload, bypassing the intended local project-directory boundary.
    Confidence: 0.95

What I checked:

  • Current main boundary: Current main exposes only get, content, run, and create under the Apps Script command group; the requested sync and deployment commands are not already implemented. (internal/cmd/appscript.go:14, eb85a99366eb)
  • Symlink upload path: The new source reader skips directories but reads every allowed-extension non-directory entry; a symlink named Code.gs is therefore followed by os.ReadFile and its target is included in UpdateContent. (internal/cmd/appscript_sync.go:223, 331b9da14b77)
  • Dependency contract applies: The patch directly imports and calls the Google Apps Script v1 client for project content and deployment operations, so its API behavior is part of this review boundary. (internal/cmd/appscript_sync.go:9, 331b9da14b77)
  • Repository direction: VISION.md classifies new API surfaces and large PRs as discuss-first work and requires live Google proof where behavior depends on Google Workspace APIs. (VISION.md:16, eb85a99366eb)
  • Current command provenance: Blame ties the current Apps Script command declarations to the v0.37.0 release commit authored by Peter Steinberger. (internal/cmd/appscript.go:14, 45b5d766e137)

Likely related people:

  • Peter Steinberger: Git blame attributes the current Apps Script command declarations to the v0.37.0 release commit authored by Peter Steinberger. (role: current command-surface provenance; confidence: medium; commits: 45b5d766e137; files: internal/cmd/appscript.go)

Rating scale

Score Internal tier Crab rank Meaning
6/6 S 🦀 challenger crab Exceptional readiness
5/6 A 🦞 diamond lobster Very strong readiness
4/6 B 🐚 platinum hermit Good normal PR; ordinary maintainer review
3/6 C 🦐 gold shrimp Useful, but confidence is limited
2/6 D 🦪 silver shellfish Proof or implementation needs work
1/6 F 🧂 unranked krab Not merge-ready
N/A NA 🌊 off-meta tidepool Rating does not apply

Overall follows the weaker of proof and patch quality.
Shiny media proof means a screenshot, video, or linked artifact directly shows the changed behavior. Runtime, network, CSP, and security claims still need visible diagnostics.

Workflow

  • ClawSweeper keeps one durable marker-backed review comment per issue or PR.
  • Re-runs edit this comment so the latest verdict, findings, and automation markers stay together instead of adding duplicate bot comments.
  • A fresh review can be triggered by eligible @clawsweeper re-review comments, exact-item GitHub events, scheduled/background review runs, or manual workflow dispatch.
  • PR/issue authors and users with repository write access can comment @clawsweeper re-review or @clawsweeper re-run on an open PR or issue to request a fresh review only.
  • Maintainers can also comment @clawsweeper review to request a fresh review only.
  • Fresh-review commands do not start repair, autofix, rebase, CI repair, or automerge.
  • Maintainer-only repair and merge flows require explicit commands such as @clawsweeper autofix, @clawsweeper automerge, @clawsweeper fix ci, or @clawsweeper address review.
  • Maintainers can comment @clawsweeper explain to ask for more context, or @clawsweeper stop to stop active automation.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

merge-risk: 🚨 security-boundary 🚨 Merging this PR could weaken sandboxing, authorization, credentials, or sensitive data. P1 Urgent regression or broken agent/channel workflow affecting real users now. rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant